列表推导中的Lambda函数


149

即使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 x: x*x for x in range(10)]比第一个更快,因为它不会重复调用外部循环函数f。
riza 2011年

@Selinap:...不,相反,您每次通过循环都在创建一个新功能的品牌。...以及创建此新函数的开销,然后调用要慢一点(无论如何在我的系统上)。
格拉特(Gerrat)2011年

@杰拉特:即使有开销,它仍然更快。但是,当然[x*x for x in range(10)]更好。
riza 2011年

34
我刚刚进入这里是为了获得Google foobar访问权限:)
Gal Margalit

Answers:


267

第一个创建单个lambda函数并调用十次。

第二个不调用该函数。它创建10个不同的lambda函数。它将所有这些放入列表中。要使其等同于第一个,您需要:

[(lambda x: x*x)(x) for x in range(10)]

或者更好:

[x*x for x in range(10)]

13
map(lambda x: x*x, range(10)),这可能是OP首先要表达的意思。
Daniel Roseman

是的,λx:x * x ..(x)似乎是信条。
staticor

[lambda x:x * x表示range(10)中的x]从本质上讲是Haskell中的一个函数
moshe beeri

@DanielRoseman,或者确切地说list(map(lambda x: x*x, range(10)))会给您[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
rrlamichhane

108

这个问题触及“著名”和“显而易见” 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变量。它没有任何跟xlambda(它在词法黯然失色!)。得到它?

我没有讨论,为什么语法优先级不是这样,这全都意味着:

[lambda x: (x*x for x in range(10))]

可能是:[[0, 1, 4, ..., 81]],或[(0, 1, 4, ..., 81)],或者我觉得最合乎逻辑的,这将是list1个元素中的一个- generator返回值。事实并非如此,这种语言无法正常工作。

但是,如果...

如果您不遮盖for变量,并在您的lambdas中使用它,该怎么办?

好吧,然后废话发生了。看这个:

[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,coefpartial这种方式传递给它时,它变成了关键字参数,因此它必须位于位置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,但最好使用各自的版本...


22
这是一个残酷和不寻常的工作面试问题。
szeitlin

1
如果我的同事不问,我可能永远也不会搜索这个答案
–ggybox

8
哇。我只是被这种荒谬的行为所咬伤。谢谢你的文章!
蚂蚁

1
极好的答案。我也碰到了这个问题。一方面,它确实指出了Python的局限性,但另一方面,它也可能是代码气味指示器。我正在将此解决方案用于玩具项目,但这可能是在生产环境中进行重组的信号。
ahota

2
为了清楚和完整起见,您可以编写最后的列表理解,例如:[partial(lambda i, x: i * x, i) for i in (1, 2)]
josoler19年

19

最大的区别是第一个示例实际上调用了lambda f(x),而第二个示例则没有。

您的第一个示例等效于,[(lambda x: x*x)(x) for x in range(10)]而第二个示例等效于[f for x in range(10)]


11

第一个

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,因此它将生成所有这些函数。


11

人们给出了很好的答案,但忘记了我认为最重要的部分:在第二个示例X中,列表理解Xlambda功能与函数的不同,它们是完全无关的。因此,第二个示例实际上与以下示例相同:

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


这是重要的一点。我投票给您,并在我的回答中对此进行了详细说明。
Tomasz Gandor

6

其他的答案是正确的,但如果你试图使功能列表,每一个不同的参数,可以被执行,下面的代码将做到这一点:

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()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.