为什么下面两个列表推导式的输出不同,即使f和lambda函数是相同的?

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

and

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

注意,type(f)和type(lambda x: x*x)返回相同的类型。


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

第二个不调用函数。它创建了10个不同的函数。它把所有这些都放在一个列表中。为了使它与第一个等价,你需要:

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

或者更好的是:

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

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

第一个例子等价于[(lambda x: x*x)(x) for x in range(10)],而第二个例子等价于[f for x in range(10)]。


第一个

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


人们给出了很好的答案,但忘记了我认为最重要的部分: 在第二个例子中,列表推导式的X与lambda函数的X并不相同,它们完全不相关。 所以第二个例子实际上是一样的

[Lambda X: X*X for I in range(10)]

range(10)上的内部迭代只负责在列表中创建10个相似的lambda函数(10个独立的但完全相似的函数-返回每个输入的幂2)。

另一方面,第一个例子的工作方式完全不同,因为迭代中的X确实与结果相互作用,对于每个迭代的值都是X*X,因此结果将是[0,1,4,9,16,25,36,49,64,81]


这个问题触及了“著名的”和“显而易见的”Python语法中非常糟糕的部分——列表理解的lambda和for哪个优先。

我不认为OP的目的是生成一个从0到9的正方形列表。如果是这样的话,我们可以给出更多的解:

squares = []
for x in range(10): squares.append(x*x)

这是命令式语法的好方法。

但这不是重点。关键是W(为什么)TF这个模糊的表达式如此反直觉?最后我有一个白痴的案例要告诉你,所以不要过早忽略我的回答(我在一次工作面试中提到过)。

因此,OP的理解返回了lambdas的列表:

[(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的内存地址——它们都是不同的!

当然,你可以有一个更“最佳”(哈哈)的版本:

>>> [lambda x: x*x] * 3
[<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]

看到了吗?3乘以相同的。

请注意,我使用_作为for变量。它与lambda中的x无关(它在词汇上被掩盖了!)。明白了吗?

我省略了讨论,为什么语法优先级不是这样,这一切都意味着:

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

可能是:[[0,1,4,…], 81]],或[(0,1,4,…, 81)],或者我认为最符合逻辑的,这将是一个1元素的列表-一个返回值的生成器。但事实并非如此,语言不是这样运作的。

但是,如果……

如果你不遮蔽for变量,并在你的lambda中使用它呢??

然后,糟糕的事情发生了。看看这个:

[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中来打破与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

(我有外部变量也= I,但我认为这是更清晰的解决方案-我引入y,以便我们都能看到哪个女巫是哪个女巫)。

编辑2019-08-30:

Following a suggestion by @josoler, which is also present in an answer by @sheridp - the value of the list comprehension "loop variable" can be "embedded" inside an object - the key is for it to be accessed at the right time. The section "After" above does it by wrapping it in another lambda and calling it immediately with the current value of i. Another way (a little bit easier to read - it produces no 'WAT' effect) is to store the value of i inside a partial object, and have the "inner" (original) lambda take it as an argument (passed supplied by the partial object at the time of the call), i.e.:

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,但每个版本都有自己的…

编辑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的演讲“从上到下,从左到右”,这也解释了为什么避免可变默认值)。


其他答案是正确的,但如果你试图创建一个函数列表,每个函数都有不同的参数,可以稍后执行,下面的代码将完成这一任务:

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()