lambda 函数闭包捕获什么? [英] What do lambda function closures capture?

查看:35
本文介绍了lambda 函数闭包捕获什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近我开始玩 Python,我发现了闭包工作方式的一些奇特之处.考虑以下代码:

adders=[无,无,无,无]对于 [0,1,2,3] 中的 i:adders[i]=lambda a: i+a打印加法器[1](3)

它构建了一个简单的函数数组,这些函数接受单个输入并返回该输入加上一个数字.函数在 for 循环中构造,其中迭代器 i0 运行到 3.对于这些数字中的每一个,都会创建一个 lambda 函数,该函数捕获 i 并将其添加到函数的输入中.最后一行以 3 作为参数调用第二个 lambda 函数.令我惊讶的是输出是 6.

我期望 4.我的推理是:在 Python 中,一切都是对象,因此每个变量都是指向它的必要指针.在为 i 创建 lambda 闭包时,我希望它存储一个指向 i 当前指向的整数对象的指针.这意味着当 i 分配一个新的整数对象时,它不应该影响之前创建的闭包.遗憾的是,在调试器中检查 adders 数组表明确实如此.所有lambda 函数都引用i3 的最后一个值,结果为adders[1](3) 返回 6.

这让我想知道以下几点:

  • 闭包准确捕获了什么?
  • 说服 lambda 函数以在 ii 的当前值的最优雅方法是什么?代码>更改其值?

解决方案

您的第二个问题已得到解答,但至于您的第一个问题:

<块引用>

闭包准确捕获了什么?

Python 中的作用域是动态和词法的.闭包将永远记住变量的名称和范围,而不是它指向的对象.由于您示例中的所有函数都在相同的作用域中创建并使用相同的变量名称,因此它们始终引用相同的变量.

关于如何克服这个问题的另一个问题,我想到了两种方法:

  1. 最简洁但不完全等效的方法是Adrien Plisson 推荐的方法.创建一个带有额外参数的 lambda,并将额外参数的默认值设置为您想要保留的对象.

  2. 每次创建 lambda 时都创建一个新的作用域,稍微冗长一点但不那么笨拙:

    <预><代码>>>>加法器 = [0,1,2,3]>>>对于 [0,1,2,3] 中的 i:... adders[i] = (lambda b: lambda a: b + a)(i)...>>>加法器[1](3)4>>>加法器[2](3)5

    此处的作用域是使用一个新函数(为简洁起见是 lambda)创建的,该函数绑定其参数,并将您想要绑定的值作为参数传递.但是,在实际代码中,您很可能会使用普通函数而不是 lambda 来创建新的作用域:

    def createAdder(x):返回 lambda y: y + xadders = [createAdder(i) for i in range(4)]

Recently I started playing around with Python and I came around something peculiar in the way closures work. Consider the following code:

adders=[None, None, None, None]

for i in [0,1,2,3]:
   adders[i]=lambda a: i+a

print adders[1](3)

It builds a simple array of functions that take a single input and return that input added by a number. The functions are constructed in for loop where the iterator i runs from 0 to 3. For each of these numbers a lambda function is created which captures i and adds it to the function's input. The last line calls the second lambda function with 3 as a parameter. To my surprise the output was 6.

I expected a 4. My reasoning was: in Python everything is an object and thus every variable is essential a pointer to it. When creating the lambda closures for i, I expected it to store a pointer to the integer object currently pointed to by i. That means that when i assigned a new integer object it shouldn't effect the previously created closures. Sadly, inspecting the adders array within a debugger shows that it does. All lambda functions refer to the last value of i, 3, which results in adders[1](3) returning 6.

Which make me wonder about the following:

  • What do the closures capture exactly?
  • What is the most elegant way to convince the lambda functions to capture the current value of i in a way that will not be affected when i changes its value?

解决方案

Your second question has been answered, but as for your first:

what does the closure capture exactly?

Scoping in Python is dynamic and lexical. A closure will always remember the name and scope of the variable, not the object it's pointing to. Since all the functions in your example are created in the same scope and use the same variable name, they always refer to the same variable.

EDIT: Regarding your other question of how to overcome this, there are two ways that come to mind:

  1. The most concise, but not strictly equivalent way is the one recommended by Adrien Plisson. Create a lambda with an extra argument, and set the extra argument's default value to the object you want preserved.

  2. A little more verbose but less hacky would be to create a new scope each time you create the lambda:

    >>> adders = [0,1,2,3]
    >>> for i in [0,1,2,3]:
    ...     adders[i] = (lambda b: lambda a: b + a)(i)
    ...     
    >>> adders[1](3)
    4
    >>> adders[2](3)
    5
    

    The scope here is created using a new function (a lambda, for brevity), which binds its argument, and passing the value you want to bind as the argument. In real code, though, you most likely will have an ordinary function instead of the lambda to create the new scope:

    def createAdder(x):
        return lambda y: y + x
    adders = [createAdder(i) for i in range(4)]
    

这篇关于lambda 函数闭包捕获什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆