2011-09-09 37 views
16

在蟒2.6:怪異的行爲:LAMBDA內部列表理解

[x() for x in [lambda: m for m in [1,2,3]]] 

結果:

[3, 3, 3] 

我期望的輸出爲[1,2,3]。即使使用非列表理解方法,我也會遇到完全相同的問題。甚至在我將m複製到另一個變量之後。

我錯過了什麼?

+0

...但是這適用於迭代器。>>> l =(lambda:m for m in [1,2,3 ]) >>> [x()for x in l] – GeneralBecos

+0

這是因爲發生器不會一次創建所有值,它在請求時創建它們。列表理解和生成器表達式不盡相同,但它們通常可以互換使用。有些情況(比如這個)行爲有顯着不同。 –

+0

爲什麼'x()'不只是'x'?它有什麼不同? – amyassin

回答

14

爲了使lambda表達式記得m值,你可以使用一個帶參數的默認值:

[x() for x in [lambda m=m: m for m in [1,2,3]]] 
# [1, 2, 3] 

這工作,因爲默認值設置一次,在定義時。每個lambda現在使用它自己的默認值m,而不是在lambda執行時在外部範圍中查找m的值。

+0

太棒了!沒想到給lambda設置一個默認值。好帖子。 –

-1

我也注意到了。我的結論是lambda只創建一次。所以實際上,你的內部列表理解將給出3個與m的最後一個值相關的縮進函數。

試試看,並檢查元素的id()。

[注意:這個答案不正確;看到評論]

+0

lambda不會創建一次。 '[[1,2,3]]中的[lambda:m]將生成三個單獨的lambda表達式。 –

+0

是的,注意到了。對不起:) ID的開始和結束看起來對我來說是一樣的... – Gerenuk

5

長話短說,你不想這樣做。更具體地說,你遇到的是操作順序問題。您正在創建三個單獨的lambda,它們全都返回m,但沒有一個立即被調用。然後,當你到達外部列表理解並且他們都被稱爲m的剩餘值是3時,內部列表理解的最後一個值。

- 徵求意見 -

>>> [lambda: m for m in range(3)] 
[<function <lambda> at 0x021EA230>, <function <lambda> at 0x021EA1F0>, <function <lambda> at 0x021EA270>] 

那是三個獨立的lambda表達式。

而且,作爲進一步的證據:

>>> [id(m) for m in [lambda: m for m in range(3)]] 
[35563248, 35563184, 35563312] 

同樣,三個獨立的ID。

+0

檢查lambda的id()。他們都是一樣的。 – Gerenuk

+0

所以這意味着lambda持有對m的引用而不是m的值。是否有可能使lambda保持變量的值?謝謝! – GeneralBecos

+0

@GeneralBecos - 不是我意識到,不,如果其他人知道某種方式並可以將其添加爲額外的評論,我會很樂意記下它。除非它傷害到你的最終結果,否則你用一個生成器的方法是合理的,並且這會產生預期的輸出:'[x()for x in(lambda:m for [in] [1,2,3])]' –

6

您遇到的效果被稱爲closures,當您定義一個引用非局部變量的函數時,函數會保留對該變量的引用,而不是獲取其自己的副本。爲了說明,我將把你的代碼擴展到一個沒有理解或lambda表達式的同等版本。

inner_list = [] 
for m in [1, 2, 3]: 
    def Lambda(): 
     return m 
    inner_list.append(Lambda) 

所以,在這一點上,inner_list中有三種功能,以及每個函數被調用時,將返回m值。但最重要的是他們都看到了同樣的m,儘管m正在改變,他們從未看過它,直到稍後調用。

outer_list = [] 
for x in inner_list: 
    outer_list.append(x()) 

特別是,由於內部列表完全構建外部列表開始被建成之前,m已經達到3其最後的值,並且所有三個功能看到相同的值。

3

看看__closure__的功能。所有3點到同一小區的對象,從外部範圍保持一個參考到m:

>>> print(*[x.__closure__[0] for x in [lambda: m for m in [1,2,3]]], sep='\n') 
<cell at 0x00D17610: int object at 0x1E2139A8> 
<cell at 0x00D17610: int object at 0x1E2139A8> 
<cell at 0x00D17610: int object at 0x1E2139A8> 

如果你不想讓你的功能,採取併購作爲關鍵字參數,按unubtu的答案,你可以而是在每次迭代中使用額外的lambda來評估m:

>>> [x() for x in [(lambda x: lambda: x)(m) for m in [1,2,3]]] 
[1, 2, 3] 
0

就我個人而言,我覺得這是一個更優雅的解決方案。 Lambda返回一個函數,所以如果我們想使用該函數,那麼我們應該使用它。在lambda和generator中爲'anonymous'變量使用相同的符號令人困惑,因此在我的示例中,我使用了不同的符號來使它更加清晰。

>>> [ (lambda a:a)(i) for i in range(3)] 
[0, 1, 2] 
>>> 

它也更快。

>>> timeit.timeit('[(lambda a:a)(i) for i in range(10000)]',number=10000) 
9.231263160705566 
>>> timeit.timeit('[lambda a=i:a for i in range(10000)]',number=10000) 
11.117988109588623 
>>> 

,但速度不如地圖:

>>> timeit.timeit('map(lambda a:a, range(10000))',number=10000) 
5.746963977813721 

(我跑這些測試多次,結果是一樣的,這是在Python 2.7版完成的,結果是在Python 3不同:兩個列表解析在性能上更接近,並且速度慢得多,地圖仍然快得多)。