最近,我开始玩弄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
,该函数捕获并添加到函数的输入中。最后一行使用参数作为参数调用第二个lambda
函数3
。令我惊讶的是6
。
我期望一个4
。我的推理是:在Python中,所有东西都是对象,因此每个变量都是指向它的指针。为创建lambda
闭包时i
,我希望它存储一个指向当前由指向的整数对象的指针i
。这意味着,当i
分配一个新的整数对象时,它不应影响先前创建的闭包。可悲的是,adders
在调试器中检查该阵列是否可以完成。所有的lambda
功能指的最后一个值i
,3
,其结果adders[1](3)
返回6
。
这让我想知道以下几点:
lambda
功能捕获当前值,i
而该方法在i
更改其值时不会受到影响?您的第二个问题已经回答,但是关于第一个问题:
闭包究竟捕获了什么?
Python的作用域是动态且词汇丰富的。闭包将始终记住变量的名称和范围,而不是其指向的对象。由于示例中的所有函数都是在相同的作用域中创建的,并且使用相同的变量名,因此它们始终引用相同的变量。
编辑:关于您如何解决此问题的另一个问题,有两种方法可以想到:
最简洁但并非严格等效的方法是Adrien Plisson推荐的方法。创建带有额外参数的lambda,并将额外参数的默认值设置为要保留的对象。
每次创建lambda时,创建一个新的作用域会更冗长一些,但hacky会更少一些:
>>> 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)]
最多,如果您为我的其他答案(简单问题)添加答案,我可以将其标记为可接受的答案。谢谢!
Python具有静态作用域,而不是动态作用域。它只是所有变量都是引用,因此,当您将变量设置为新对象时,变量本身(引用)具有相同的位置,但指向其他对象。如果您在Scheme中也会发生相同的情况
set!
。有关动态范围的真正含义,请参见此处:voidspace.org.uk/python/articles/code_blocks.shtml。选项2类似于功能语言称为“ Curried函数”。