在蟒2.6:怪異的行爲:LAMBDA內部列表理解
[x() for x in [lambda: m for m in [1,2,3]]]
結果:
[3, 3, 3]
我期望的輸出爲[1,2,3]。即使使用非列表理解方法,我也會遇到完全相同的問題。甚至在我將m複製到另一個變量之後。
我錯過了什麼?
在蟒2.6:怪異的行爲:LAMBDA內部列表理解
[x() for x in [lambda: m for m in [1,2,3]]]
結果:
[3, 3, 3]
我期望的輸出爲[1,2,3]。即使使用非列表理解方法,我也會遇到完全相同的問題。甚至在我將m複製到另一個變量之後。
我錯過了什麼?
爲了使lambda表達式記得m
值,你可以使用一個帶參數的默認值:
[x() for x in [lambda m=m: m for m in [1,2,3]]]
# [1, 2, 3]
這工作,因爲默認值設置一次,在定義時。每個lambda現在使用它自己的默認值m
,而不是在lambda執行時在外部範圍中查找m
的值。
太棒了!沒想到給lambda設置一個默認值。好帖子。 –
我也注意到了。我的結論是lambda只創建一次。所以實際上,你的內部列表理解將給出3個與m的最後一個值相關的縮進函數。
試試看,並檢查元素的id()。
[注意:這個答案不正確;看到評論]
lambda不會創建一次。 '[[1,2,3]]中的[lambda:m]將生成三個單獨的lambda表達式。 –
是的,注意到了。對不起:) ID的開始和結束看起來對我來說是一樣的... – Gerenuk
長話短說,你不想這樣做。更具體地說,你遇到的是操作順序問題。您正在創建三個單獨的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。
檢查lambda的id()。他們都是一樣的。 – Gerenuk
所以這意味着lambda持有對m的引用而不是m的值。是否有可能使lambda保持變量的值?謝謝! – GeneralBecos
@GeneralBecos - 不是我意識到,不,如果其他人知道某種方式並可以將其添加爲額外的評論,我會很樂意記下它。除非它傷害到你的最終結果,否則你用一個生成器的方法是合理的,並且這會產生預期的輸出:'[x()for x in(lambda:m for [in] [1,2,3])]' –
您遇到的效果被稱爲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其最後的值,並且所有三個功能看到相同的值。
看看__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]
就我個人而言,我覺得這是一個更優雅的解決方案。 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不同:兩個列表解析在性能上更接近,並且速度慢得多,地圖仍然快得多)。
...但是這適用於迭代器。>>> l =(lambda:m for m in [1,2,3 ]) >>> [x()for x in l] – GeneralBecos
這是因爲發生器不會一次創建所有值,它在請求時創建它們。列表理解和生成器表達式不盡相同,但它們通常可以互換使用。有些情況(比如這個)行爲有顯着不同。 –
爲什麼'x()'不只是'x'?它有什麼不同? – amyassin