(lambda)函数闭包捕获什么?
最近我开始使用Python进行游戏,并且在关闭工作方式中遇到了一些特殊问题。 考虑下面的代码:
adders=[0,1,2,3]
for i in [0,1,2,3]:
adders[i]=lambda a: i+a
print adders[1](3)
它构建了一个简单的函数数组,它接受单个输入并返回由数字添加的输入。 这些函数在for
循环中构建,其中迭代器i
从0
运行到3
。 对于这些数字中的每一个,都会创建一个lambda
函数,它捕获i
并将其添加到函数的输入中。 最后一行以3
作为参数调用第二个lambda
函数。 令我惊讶的是,输出是6
。
我预计4
。 我的推理是:在Python中,所有东西都是一个对象,因此每个变量都是必不可少的指针。 为i
创建lambda
关闭时,我期望它存储一个指向当前由i
指向的整数对象的指针。 这意味着当i
分配一个新的整数对象时,它不应该影响先前创建的闭包。 令人遗憾的是,在调试器中检查adders
数组表明它确实如此。 所有的lambda
函数都指向i
的最后一个值3
,这会导致adders[1](3)
返回6
。
这让我想到以下内容:
lambda
函数捕捉i
的当前值的最优雅的方式是什么,当i
改变它的值时不会受到影响? 你的第二个问题已经得到解答,但至于你的第一个问题:
闭包究竟捕获了什么?
Python中的范围界定是动态的和词汇的。 闭包将永远记住变量的名称和范围,而不是它指向的对象。 由于示例中的所有函数都在相同的作用域中创建并使用相同的变量名,因此它们始终引用相同的变量。
编辑:关于如何克服这个问题的其他问题,有两种方法可以想到:
最简洁但并非完全等同的方法是Adrien Plisson推荐的方法。 创建一个带额外参数的lambda表达式,并将额外参数的默认值设置为您要保留的对象。
稍微冗长些,但不太冒昧的做法是每次创建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
这里的范围是使用一个新的函数(一个lambda,为了简洁)创建的,它绑定了它的参数,并将你想绑定的值作为参数传递。 但是,在真实代码中,您很可能会使用普通函数而不是lambda来创建新范围:
def createAdder(x):
return lambda y: y + x
adders = [createAdder(i) for i in range(4)]
您可以使用具有默认值的参数强制捕获变量:
>>> for i in [0,1,2,3]:
... adders[i]=lambda a,i=i: i+a # note the dummy parameter with a default value
...
>>> print( adders[1](3) )
4
这个想法是声明一个参数(巧妙地命名为i
)并给它一个你想捕获的变量的默认值( i
的值)
为了完整性,您的第二个问题的另一个答案:您可以在functools模块中使用partial。
随着Chris Lutz提出的从运营商导入添加例子变为:
from functools import partial
from operator import add # add(a, b) -- Same as a + b.
adders = [0,1,2,3]
for i in [0,1,2,3]:
# store callable object with first argument given as (current) i
adders[i] = partial(add, i)
print adders[1](3)
链接地址: http://www.djcxy.com/p/51267.html