簡體   English   中英

列表推導式中的 Lambda 函數

[英]Lambda function in list comprehensions

為什么以下兩個列表推導式的輸出不同,即使flambda函數相同?

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變量。 它與lambdax無關(它在詞匯上被掩蓋了!)。 得到它?

我省略了討論,為什么語法優先級不是這樣,這意味着:

[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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM