我有两个函数返回一个函数列表。这些函数使用数字x
并向其添加i
。 i
是一个从0-9增加的整数。
def test_without_closure():
return [lambda x: x+i for i in range(10)]
def test_with_yield():
for i in range(10):
yield lambda x: x+i
我希望test_without_closure
返回10个函数的列表,每个函数都会9
添加x
,因为i
的值为9
print sum(t(1) for t in test_without_closure()) # prints 100
我预计test_with_yield
也会有相同的行为,但它会正确创建10个函数。
print sum(t(1) for t in test_with_yield()) # print 55
我的问题是,是否会在Python中形成一个闭包?
答案 0 :(得分:29)
Yielding不会在Python中创建闭包,lambdas会创建一个闭包。你在“test_without_closure”中得到所有9个的原因并不是没有关闭。如果没有,您根本无法访问i
。问题是所有闭包都包含对同一个i变量的引用¹,该函数在函数末尾为9。
这种情况在test_with_yield
中差别不大。那么,为什么你会得到不同的结果?因为yield
暂停函数的运行,所以在函数结束之前,即在i
为9之前,可以使用生成的lambdas。要查看这意味着什么,请考虑以下两个使用test_with_yield
:
[f(0) for f in test_with_yield()]
# Result: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[f(0) for f in list(test_with_yield())]
# Result: [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
这里发生的是第一个例子产生一个lambda(而我是0),调用它(我仍然是0),然后推进该函数直到另一个lambda被产生(我现在是1),调用lambda,等等。重要的是在控制流返回test_with_yield
之前(即在i的值改变之前)调用每个lambda。
在第二个例子中,我们首先创建一个列表。所以第一个lambda被生成(i为0)并放入列表中,第二个lambda被创建(我现在是1)并被放入列表中......直到最后一个lambda被生成(我现在是9)并且放入进入清单。然后然后我们开始调用lambdas。所以,由于i
现在为9,所有lambda都返回9。
¹这里重要的一点是闭包持有对变量的引用,而不是创建闭包时它们所持有的值的副本。这样,如果你在lambda(或者内部函数中)中分配变量,它就像lambdas那样创建闭包,这也会改变lambda之外的变量,如果你改变外面的值,那么这个变化将是在lambda内可见。
答案 1 :(得分:7)
不,屈服与闭包无关。
以下是如何识别Python中的闭包:闭包是
一个功能
执行非限定名称查找
函数本身不存在名称绑定
但名称的绑定存在于函数的本地范围内,该函数的定义包含查找名称的函数的定义。
您观察到的行为差异的原因是懒惰,而不是与闭包有关。比较和对比以下
def lazy():
return ( lambda x: x+i for i in range(10) )
def immediate():
return [ lambda x: x+i for i in range(10) ]
def also_lazy():
for i in range(10):
yield lambda x:x+i
not_lazy_any_more = list(also_lazy())
print( [ f(10) for f in lazy() ] ) # 10 -> 19
print( [ f(10) for f in immediate() ] ) # all 19
print( [ f(10) for f in also_lazy() ] ) # 10 -> 19
print( [ f(10) for f in not_lazy_any_more ] ) # all 19
请注意,第一个和第三个示例给出了相同的结果,第二个和第四个示例也是如此。第一个和第三个是懒惰的,第二个和第四个不是。
请注意,所有四个示例都提供了一堆关于i
的最新绑定的闭包,只是在第一个第三个案例中,您在之前评估闭包 / em>重新绑定i
(甚至在您创建序列中的下一个闭包之前),而在第二个和第四个案例中,您首先等待i
反弹到9(在您之后)我创建并收集了你要制作的所有闭包,然后只评估闭包。
答案 2 :(得分:3)
添加到@ sepp2k的答案你会看到这两种不同的行为,因为正在创建的lambda
函数不知道它们必须从哪里得到i
的值。在创建此函数时,它只知道它必须从本地范围,封闭范围,全局范围或内置函数中获取i
的值。
在这种特殊情况下,它是一个闭包变量(封闭范围)。它的价值随着每次迭代而变化。
这是因为每次你产生一个lambda
函数时,生成器函数的执行在那一刻停止,当你调用它时,它将在那一刻使用i
的值。但在第一种情况下,在调用任何函数之前,我们已经将i
的值提升为9。
为证明这一点,您可以从i
的单元格内容中获取__closure__
的当前值:
>>> for func in test_with_yield():
print "Current value of i is {}".format(func.__closure__[0].cell_contents)
print func(9)
...
Current value of i is 0
Current value of i is 1
Current value of i is 2
Current value of i is 3
Current value of i is 4
Current value of i is 5
Current value of i is 6
...
但是如果你将函数存储在某处并稍后调用它们,那么你将看到与第一次相同的行为:
from itertools import islice
funcs = []
for func in islice(test_with_yield(), 4):
print "Current value of i is {}".format(func.__closure__[0].cell_contents)
funcs.append(func)
print '-' * 20
for func in funcs:
print "Now value of i is {}".format(func.__closure__[0].cell_contents)
<强>输出:强>
Current value of i is 0
Current value of i is 1
Current value of i is 2
Current value of i is 3
--------------------
Now value of i is 3
Now value of i is 3
Now value of i is 3
Now value of i is 3
Patrick Haugh in comments使用的示例也显示了相同的内容:sum(t(1) for t in list(test_with_yield()))
将i
指定为lambda
的默认值,默认值是在创建函数时计算的,并且不会更改(unless it's a mutable object)。 i
现在是lambda
函数的局部变量。
>>> def test_without_closure():
return [lambda x, i=i: x+i for i in range(10)]
...
>>> sum(t(1) for t in test_without_closure())
55