为什么Python yield语句形成闭包?
我有两个函数返回一个函数列表。 该功能采取了一些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中产生闭包?
Yielding不会在Python中创建闭包,lambda会创建闭包。 你在“test_without_closure”中获得所有9的原因并不是没有关闭。 如果没有,你根本无法访问i
。 问题是所有的闭包都包含一个相同的i变量的参考,这个变量在函数的结尾是9。
这种情况在test_with_yield
没有太大的区别。 那么,为什么你会得到不同的结果? 由于yield
暂停了函数的运行,因此可以在函数结束之前(即i
之前)使用已生成的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,等等。 重要的是每个lambda在控制流返回到test_with_yield
之前被调用(即在i的值改变之前)。
在第二个例子中,我们首先创建一个列表。 因此,第一个lambda被放弃(我是0)并放入列表中,第二个lambda被创建(我现在是1)并放入列表中...直到最后一个lambda被放弃(我现在是9)并放入列入清单。 然后我们开始调用lambda表达式。 所以,因为i
现在是9,所有的lambda都返回9。
¹这里最重要的是闭包持有对变量的引用,而不是创建闭包时所持有的值的副本。 这样,如果你在一个lambda(或者内部函数,它创建与lambda类似的方式创建闭包)中分配给变量,这也会改变lambda之外的变量,如果你改变外部值,那么这个变化将会是在lambda内可见。
不,屈服与封闭无关。
以下是如何识别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
最近绑定的闭包,只是在第一个第三种情况下,在重新绑定i
之前评估闭包(即使在序列中创建了下一个闭包之前),而在第二和第四种情况下,你首先等到i
被反弹到9(在你创建并收集了你将要完成的所有闭包之后),然后才评估闭包。
添加到@ sepp2k的答案,你会看到这两种不同的行为,因为创建的lambda
函数不知道他们必须从哪里获得i
的价值。 在创建这个函数的时候,它知道它必须从本地作用域,封闭作用域,全局作用域或内置函数中获取i
的值。
在这个特殊情况下,它是一个闭包变量(封闭范围)。 其价值随着每次迭代而变化。
在Python中查看LEGB。
现在为什么第二个按预期工作,但不是第一个?
这是因为每次你产生一个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在注释中使用的示例还显示了同样的事情: sum(t(1) for t in list(test_with_yield()))
正确的方法:
将i
作为默认值分配给lambda
,默认值是在函数创建时计算的,它们不会改变(除非它是可变对象)。 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
链接地址: http://www.djcxy.com/p/53149.html
上一篇: Why do Python yield statements form a closure?
下一篇: Is there a need to close files that have no reference to them?