[英]Lambda function in list comprehensions
為什么以下兩個列表推導式的輸出不同,即使f
和lambda
函數相同?
f = lambda x: x*x
[f(x) for x in range(10)]
和
[lambda x: x*x for x in range(10)]
請注意, type(f)
和type(lambda x: x*x)
返回相同的類型。
第一個創建一個 lambda 函數並調用它十次。
第二個不調用該函數。 它創建了 10 個不同的 lambda 函數。 它把所有這些都放在一個列表中。 要使其等同於您需要的第一個:
[(lambda x: x*x)(x) for x in range(10)]
或者更好:
[x*x for x in range(10)]
這個問題觸及了“著名”和“顯而易見”的 Python 語法的一個非常糟糕的部分——什么優先,lambda 或列表理解的 for。
我不認為 OP 的目的是生成從 0 到 9 的方格列表。如果是這樣,我們可以提供更多解決方案:
squares = []
for x in range(10): squares.append(x*x)
但這不是重點。 關鍵是 W(hy)TF 這個模棱兩可的表達是否違反直覺? 最后我有一個愚蠢的案例給你,所以不要過早地駁回我的回答(我在求職面試中得到了它)。
因此,OP 的理解返回了一個 lambda 列表:
[(lambda x: x*x) for x in range(10)]
這當然只是平方函數的 10 個不同副本,請參閱:
>>> [lambda x: x*x for _ in range(3)]
[<function <lambda> at 0x00000000023AD438>, <function <lambda> at 0x00000000023AD4A8>, <function <lambda> at 0x00000000023AD3C8>]
注意lambdas 的內存地址——它們都是不同的!
你當然可以有這個表達式的更“最佳”(哈哈)版本:
>>> [lambda x: x*x] * 3
[<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]
看? 3 次相同的lambda。
請注意,我使用_
作為for
變量。 它與lambda
的x
無關(它在詞匯上被掩蓋了!)。 得到它?
我省略了討論,為什么語法優先級不是這樣,這意味着:
[lambda x: (x*x for x in range(10))]
這可能是: [[0, 1, 4, ..., 81]]
或[(0, 1, 4, ..., 81)]
,或者我認為最合乎邏輯的,這將是一個list
1 個元素 - 返回值的generator
。 事實並非如此,語言不是這樣工作的。
但是,如果……
如果你不掩蓋for
變量,並在你的lambda
s 中使用它怎么辦???
那么,廢話就發生了。 看這個:
[lambda x: x * i for i in range(4)]
這當然意味着:
[(lambda x: x * i) for i in range(4)]
但這並不意味着:
[(lambda x: x * 0), (lambda x: x * 1), ... (lambda x: x * 3)]
這太瘋狂了!
列表推導式中的 lambda 表達式是該推導式范圍的閉包。 一個詞法閉包,所以它們通過引用引用i
,而不是它們被評估時的值!
所以,這個表達式:
[(lambda x: x * i) for i in range(4)]
大致相當於:
[(lambda x: x * 3), (lambda x: x * 3), ... (lambda x: x * 3)]
我相信我們可以在這里使用 python 反編譯器看到更多內容(我的意思是例如dis
模塊),但是對於 Python-VM 不可知論的討論,這已經足夠了。 面試問題到此為止。
現在,如何制作一個真正乘以連續整數的乘法器 lambda list
? 那么,接受的答案一樣,我們需要打破直接領帶i
在另一個包裹它lambda
,這是越來越稱為列表解析表達式中:
前:
>>> a = [(lambda x: x * i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
2
后:
>>> a = [(lambda y: (lambda x: y * x))(i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
1
(我也有外部 lambda 變量 = i
,但我認為這是更清晰的解決方案 - 我引入了y
以便我們都可以看到哪個女巫是哪個)。
編輯 2019-08-30:
遵循@josoler 的建議,該建議也出現在@sheridp 的回答中——列表推導式“循環變量”的值可以“嵌入”在對象中——關鍵是要在正確的時間訪問它。 上面的“After”部分通過將它包裝在另一個lambda
並立即使用i
的當前值調用它來完成它。 另一種方法(更容易閱讀 - 它不會產生“WAT”效果)是將i
的值存儲在partial
對象中,並讓“內部”(原始) lambda
將其作為參數(由調用時的partial
對象),即:
2后:
>>> from functools import partial
>>> a = [partial(lambda y, x: y * x, i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)
太棒了,但仍然有一點小麻煩! 假設我們不想讓代碼閱讀器更容易,並按名稱傳遞因子(作為關鍵字參數給partial
)。 讓我們做一些重命名:
2.5 之后:
>>> a = [partial(lambda coef, x: coef * x, coef=i) for i in (1, 2)]
>>> a[0](1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() got multiple values for argument 'coef'
哇?
>>> a[0]()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'x'
等等...我們正在將參數的數量更改為 1,並從“太多”變為“太少”?
好吧,它不是真正的 WAT,當我們以這種方式將coef
傳遞給partial
時,它變成了關鍵字參數,因此它必須在位置x
參數之后,如下所示:
3后:
>>> a = [partial(lambda x, coef: coef * x, coef=i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)
我更喜歡最后一個版本而不是嵌套的 lambda,但每個人都有自己的......
編輯 2020-08-18:
感謝評論者 dasWesen,我發現 Python 文檔中涵蓋了這些內容: https ://docs.python.org/3.4/faq/programming.html#why-do-lambdas-defined-in-a-loop- with-different-values-all-return-the-same-result - 它處理循環而不是列表推導式,但想法是相同的 - lambda 函數中的全局或非局部變量訪問。 甚至有一個解決方案 - 使用默認參數值(如任何函數):
>>> a = [lambda x, coef=i: coef * x for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)
這樣 coef 值在函數定義時綁定到 i 的值(參見 James Powell 的演講“自上而下,從左到右”,這也解釋了為什么要避開可變默認值)。
最大的區別是第一個示例實際上調用了 lambda f(x)
,而第二個示例沒有。
您的第一個示例等效於[(lambda x: x*x)(x) for x in range(10)]
而您的第二個示例等效於[f for x in range(10)]
。
人們給了很好的答案,但忘了提,我認為最重要的部分:在第二個例子中, X
列表理解是不一樣的X
中的lambda
功能,他們是完全無關的。 所以第二個例子實際上是一樣的:
[Lambda X: X*X for I in range(10)]
range(10)
上的內部迭代僅負責在列表中創建 10 個相似的 lambda 函數(10 個單獨的函數但完全相似 - 返回每個輸入的 2 次冪)。
另一方面,第一個示例的工作方式完全不同,因為迭代的 X DO 與結果交互,對於每次迭代,值是X*X
所以結果將是[0,1,4,9,16,25, 36, 49, 64 ,81]
第一個
f = lambda x: x*x
[f(x) for x in range(10)]
對范圍內的每個值運行f()
所以它對每個值執行f(x)
第二個
[lambda x: x*x for x in range(10)]
為列表中的每個值運行 lambda,因此它生成所有這些函數。
其他的答案是正確的,但如果你試圖使功能列表,每一個不同的參數,可以在以后執行,下面的代碼將做到這一點:
import functools
a = [functools.partial(lambda x: x*x, x) for x in range(10)]
b = []
for i in a:
b.append(i())
In [26]: b
Out[26]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
雖然這個例子是人為的,但當我想要一個函數列表時,我發現它很有用,每個函數都打印不同的東西,即
import functools
a = [functools.partial(lambda x: print(x), x) for x in range(10)]
for i in a:
i()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.