最近我开始摆弄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如何实现闭包的技术细节。参见早期绑定和晚期绑定的区别是什么?有关术语讨论。
在回答你的第二个问题时,最优雅的方法是使用一个接受两个形参的函数而不是数组:
add = lambda a, b: a + b
add(1, 3)
但是,在这里使用lambda有点愚蠢。Python为我们提供了操作符模块,该模块为基本操作符提供了功能接口。上面的lambda只调用加法运算符就有不必要的开销:
from operator import add
add(1, 3)
我理解您在玩游戏,试图探索这门语言,但我无法想象在使用一组函数时,Python的奇怪作用域会成为阻碍。
如果你想,你可以写一个小的类,使用你的数组索引语法:
class Adders(object):
def __getitem__(self, item):
return lambda a: a + item
adders = Adders()
adders[1](3)
在回答你的第二个问题时,最优雅的方法是使用一个接受两个形参的函数而不是数组:
add = lambda a, b: a + b
add(1, 3)
但是,在这里使用lambda有点愚蠢。Python为我们提供了操作符模块,该模块为基本操作符提供了功能接口。上面的lambda只调用加法运算符就有不必要的开销:
from operator import add
add(1, 3)
我理解您在玩游戏,试图探索这门语言,但我无法想象在使用一组函数时,Python的奇怪作用域会成为阻碍。
如果你想,你可以写一个小的类,使用你的数组索引语法:
class Adders(object):
def __getitem__(self, item):
return lambda a: a + item
adders = Adders()
adders[1](3)
下面是一个新示例,它突出显示了闭包的数据结构和内容,以帮助阐明何时“保存”封闭上下文。
def make_funcs():
i = 42
my_str = "hi"
f_one = lambda: i
i += 1
f_two = lambda: i+1
f_three = lambda: my_str
return f_one, f_two, f_three
f_1, f_2, f_3 = make_funcs()
闭包中包含什么?
>>> print f_1.func_closure, f_1.func_closure[0].cell_contents
(<cell at 0x106a99a28: int object at 0x7fbb20c11170>,) 43
值得注意的是,my_str不在f1的闭包中。
f2的闭包里有什么?
>>> print f_2.func_closure, f_2.func_closure[0].cell_contents
(<cell at 0x106a99a28: int object at 0x7fbb20c11170>,) 43
注意(从内存地址)两个闭包包含相同的对象。所以,你可以开始认为函数有一个作用域的引用。然而,my_str不在f_1或f_2的闭包中,i也不在f_3的闭包中(未显示),这表明闭包对象本身是不同的对象。
闭包对象本身是相同的对象吗?
>>> print f_1.func_closure is f_2.func_closure
False