为什么Python yield语句形成一个闭包?

Ale*_*lex 24 python closures functional-programming 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
Run Code Online (Sandbox Code Playgroud)

我希望test_without_closure返回一个包含10个函数的列表,每个函数都添加9xi的值以来9.

print sum(t(1) for t in test_without_closure()) # prints 100
Run Code Online (Sandbox Code Playgroud)

我希望它test_with_yield也会有相同的行为,但它正确地创建了10个函数.

print sum(t(1) for t in test_with_yield()) # print 55
Run Code Online (Sandbox Code Playgroud)

我的问题是,在Python中屈服形成一个闭包吗?

sep*_*p2k 29

Yielding不会在Python中创建闭包,lambdas会创建一个闭包.你在"test_without_closure"中得到所有9个的原因并不是没有关闭.如果没有,你将根本无法访问i.问题是所有闭包都包含对同一i变量的引用¹,该函数在函数末尾为9.

这种情况并没有太大的不同test_with_yield.那么,为什么你会得到不同的结果?因为yield暂停函数的运行,所以可以在函数结束之前使用生成的lambdas,即在i9 之前.要查看这意味着什么,请考虑以下两个使用示例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]
Run Code Online (Sandbox Code Playgroud)

这里发生的是第一个例子产生一个lambda(而我是0),调用它(我仍然是0),然后推进该函数,直到产生另一个lambda(我现在是1),调用lambda,依此类推.重要的是在控制流返回test_with_yield之前(即在i的值改变之前)调用每个lambda .

在第二个示例中,我们首先创建一个列表.所以第一个lambda被生成(i为0)并放入列表中,第二个lambda被创建(我现在是1)并被放入列表中......直到最后一个lambda被生成(我现在是9)并且放入进入清单.而随后我们开始调用lambda表达式.所以从i现在开始9岁,所有的lambda都返回9.


¹这里重要的一点是闭包持有对变量的引用,而不是创建闭包时它们所持有的值的副本.这样,如果你在lambda(或内部函数中)中分配变量,它会以与lambda相同的方式创建闭包,这也会改变lambda之外的变量,如果你改变外面的值,那么这个变化将是在lambda里面可见.

  • @MoinuddinQuadri lambdas没有任何i值,他们提到了i.因此,如果我改变,通过该引用可以看到该改变. (5认同)
  • @jacg,我认为你误读了双重否定.提问者显然认为没有关闭,但这是错误的,所以这不是全9结果的原因; 原因是所有的lambdas都是闭包,正如你们所说的那样. (5认同)
  • @PatrickHaugh` [lambda x,i = i:x + i for i in range(10)]`将返回预期的lambdas. (4认同)
  • 请参阅此处:http://docs.python-guide.org/en/latest/writing/gotchas/ section"Late Binding Closures" (2认同)

jac*_*acg 7

不,屈服与封闭无关.

以下是如何识别Python中的闭包:闭包是

  1. 一个功能

  2. 其中执行非限定名称查找

  3. 函数本身不存在名称绑定

  4. 但是名称的绑定存在于函数的局部作用域中,该函数的定义包含查找名称的函数的定义.

你观察到的行为差异的原因是懒惰,而不是与闭包有关.比较和对比以下内容

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 
Run Code Online (Sandbox Code Playgroud)

请注意,第一个和第三个示例给出了相同的结果,第二个和第四个示例也是如此.第一个和第三个是懒惰的,第二个和第四个不是.

请注意,所有四个示例都提供了一堆关于最近绑定的闭包i,只是在第一个第三个案例中,您重新绑定i之前评估闭包(甚至在您创建序列中的下一个闭包之前),而在第二种情况和第四种情况,你先等到i反弹到9(在你创建并收集了你将要制作的所有闭包之后),然后再评估闭包.