最近我开始摆弄Python,发现闭包的工作方式有些特殊。考虑下面的代码:
adders=[None, None, None, None]
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。
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.
这让我想知道以下几点:
闭包究竟捕获了什么? 什么是最优雅的方法来说服lambda函数以一种不受i改变其值的影响的方式捕获i的当前值?
关于这个问题更容易理解、更实用的版本,具体到使用循环(或列表推导式、生成器表达式等)的情况,请参见在循环(或推导式)中创建函数(或lambdas)。这个问题的重点是理解Python中代码的底层行为。
如果您在这里试图解决在Tkinter中创建按钮的问题,请尝试Tkinter在for循环中创建按钮,传递命令参数以获得更具体的建议。
查看到底是什么包含在obj.__closure__?以了解Python如何实现闭包的技术细节。参见早期绑定和晚期绑定的区别是什么?有关术语讨论。