2013-11-04 120 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']