9

以下測試失敗:爲什麼map()和列表理解的結果是不同的?

#!/usr/bin/env python 
def f(*args): 
    """ 
    >>> t = 1, -1 
    >>> f(*map(lambda i: lambda: i, t)) 
    [1, -1] 
    >>> f(*(lambda: i for i in t)) # -> [-1, -1] 
    [1, -1] 
    >>> f(*[lambda: i for i in t]) # -> [-1, -1] 
    [1, -1] 
    """ 
    alist = [a() for a in args] 
    print(alist) 

if __name__ == '__main__': 
    import doctest; doctest.testmod() 

換句話說:

>>> t = 1, -1 
>>> args = [] 
>>> for i in t: 
... args.append(lambda: i) 
... 
>>> map(lambda a: a(), args) 
[-1, -1] 
>>> args = [] 
>>> for i in t: 
... args.append((lambda i: lambda: i)(i)) 
... 
>>> map(lambda a: a(), args) 
[1, -1] 
>>> args = [] 
>>> for i in t: 
... args.append(lambda i=i: i) 
... 
>>> map(lambda a: a(), args) 
[1, -1] 
+3

對於像我這樣的讀者來說,最初沒有注意到任何問題:注意`[ - 1,-1]`!本質上`lambda i:...`在循環中不捕獲i的當前值。與Python相關的 – 2010-01-07 17:27:13

+0

常見問題解答:[爲什麼使用不同值的循環中定義的lambda都返回相同的結果?](https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined -in-a-loop-with-different-values-all-return-the-same-result) – jfs 2016-10-05 15:11:02

回答

9

他們是不同的,因爲i兩個發電機表達式的值和列表comp懶惰地評估,即當在f中調用匿名函數時。
那時候,如果t是-1,那麼i被綁定到最後一個值。

所以基本上,這是該列表理解做什麼(同樣爲genexp):

x = [] 
i = 1 # 1. from t 
x.append(lambda: i) 
i = -1 # 2. from t 
x.append(lambda: i) 

現在圍繞lambda表達式引用i封閉進行,但i勢必-1在這兩種情況下,因爲這是它分配給的最後一個值。

如果你想確保拉姆達接收的i當前值,做

f(*[lambda u=i: u for i in t]) 

這樣,你強制i評價在創建關閉的時間。

編輯:生成器表達式和列表推導之間有一個區別:後者將循環變量泄漏到周圍的作用域中。

5

拉姆達捕獲變量,不值,因此代碼

lambda : i 

將始終返回i的值目前必然在關閉。當它被調用的時候,這個值已經被設置爲-1。

爲了得到你想要的東西,你需要獲得實際的結合在拉姆達創建時,借:

>>> f(*(lambda i=i: i for i in t)) # -> [-1, -1] 
[1, -1] 
>>> f(*[lambda i=i: i for i in t]) # -> [-1, -1] 
[1, -1] 
3

表達f = lambda: i等效於:

def f(): 
    return i 

表達g = lambda i=i: i等效於:

def g(i=i): 
    return i 

i是在第一種情況下一個free variable並且它被綁定到的函數參數在第二種情況下即在這種情況下它是一個局部變量。默認參數的值在函數定義時進行評估。

發生器表達是最近的封閉範圍(其中i被定義),用於i名稱在lambda表達,因此i是在該塊中解決的問題:

f(*(lambda: i for i in (1, -1)) # -> [-1, -1] 

ilambda i: ...塊的局部變量,因此它所指的對象在該塊中被定義爲:

f(*map(lambda i: lambda: i, (1,-1))) # -> [1, -1] 
相關問題