2013-11-04 124 views
6

我偶然发现了python中的行为,我很难理解。这证明了概念代码:循环中的奇怪lambda行为

from functools import partial 

if __name__ == '__main__': 
    sequence = ['foo', 'bar', 'spam'] 
    loop_one = lambda seq: [lambda: el for el in seq] 
    no_op = lambda x: x 
    loop_two = lambda seq: [partial(no_op, el) for el in seq] 
    for func in (loop_one, loop_two): 
     print [f() for f in func(sequence)] 

上面的输出是:

['spam', 'spam', 'spam'] 
['foo', 'bar', 'spam'] 

loop_one的行为令我感到诧异,因为我希望它表现为loop_twoel是一个不可变的值(一个字符串),在每个循环中都会改变,但lambda似乎存储了一个指向“循环变量”的指针,就像循环会为该序列的每个元素回收相同的内存地址一样。

上述行为与其中使用for循环的全功能函数相同(所以它不是列表理解语法)。

但等一等:还有更多...更令人费解!

以下脚本就像loop_one

b = [] 
for foo in ("foo", "bar"): 
    b.append(lambda: foo) 

print [a() for a in b] 

(输出:['bar', 'bar']

但看会发生什么,当一个与a替代变量名foo

b = [] 
for a in ("foo", "bar"): 
    b.append(lambda: a) 

print [a() for a in b] 

(输出:[<function <lambda> at 0x25cce60>, <function <lambda> at 0x25cced8>]

想知道这里发生了什么吗?我怀疑必须与我的解释器的底层C实现有关,但我没有其他任何东西(Jthon,PyPy或类似的东西)来测试这种行为在不同的实现中是否一致。

回答

4

loop_one使用的功能lambda: el指的是未在局部范围内定义的变量el。按照所谓的LEGB rule

lambda seq: [lambda: el for el in seq] 

:因此,Python将它旁边的其他lambda的封闭范围。

lambda: el被调用时,这个封闭的lambda已经(当然)已经被调用并且已经评估了列表理解。在列表理解中使用的el是这个封闭lambda中的局部变量。它的值是Python在lambda: el中查找el的值时返回的值。 el的值是相同的对于列表理解中的所有不同lambda: el函数:它是在for el in seq循环中分配给el的最后一个值。因此,el始终为'spam',最后一个值为seq


你已经找到了一个解决办法,用封闭如您loop_two。另一种方法是将el定义为具有默认值的局部变量:

loop_one = lambda seq: [lambda el=el: el for el in seq] 
3

变量(foo在以下示例中)不是在创建lambda时绑定的,而是在lambda被调用时绑定的。

>>> b = [] 
>>> for foo in ("foo", "bar"): 
...  b.append(lambda: foo) 
... 
>>> foo = "spam" 
>>> print [a() for a in b] 
['spam', 'spam'] 

>>> b = [] 
>>> for foo in ("foo", "bar"): 
...  b.append(lambda foo=foo: foo) 
... 
>>> print [a() for a in b] 
['foo', 'bar']