问题:列表推导中的Lambda函数
即使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)
返回相同的类型。
回答 0
第一个创建单个lambda函数并调用十次。
第二个不调用该函数。它创建10个不同的lambda函数。它将所有这些放入列表中。要使其等同于第一个,您需要:
[(lambda x: x*x)(x) for x in range(10)]
或者更好:
[x*x for x in range(10)]
回答 1
这个问题触及“著名”和“显而易见” 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>]
注意 lambda的内存地址-它们都是不同的!
您当然可以使用以下表达式的“最佳”(haha)版本:
>>> [lambda x: x*x] * 3
[<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]
看到?3次相同的 lambda。
请注意,我用_
的for
变量。它没有任何跟x
在lambda
(它在词法黯然失色!)。得到它?
我没有讨论,为什么语法优先级不是这样,这全都意味着:
[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的讨论来说,这就足够了。求职面试的问题就这么多了。
现在,如何使list
乘数lambda真正乘以连续的整数?好吧,类似于接受的答案,我们需要通过将直接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的答案中)-列表理解“循环变量”的值可以“嵌入”对象内-关键是要在正确的时间访问它。上面的“之后”部分通过将其包装在另一个包装中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'
WAT?
>>> 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,但最好使用各自的版本…
回答 2
最大的区别是第一个示例实际上调用了lambda f(x)
,而第二个示例则没有。
您的第一个示例等效于,[(lambda x: x*x)(x) for x in range(10)]
而第二个示例等效于[f for x in range(10)]
。
回答 3
第一个
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,因此它将生成所有这些函数。
回答 4
人们给出了很好的答案,但忘记了我认为最重要的部分:在第二个示例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]
回答 5
其他的答案是正确的,但如果你试图使功能列表,每一个不同的参数,可以被执行后,下面的代码将做到这一点:
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()