问题:列表推导中的Lambda函数

即使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)返回相同的类型。

Why is the output of the following two list comprehensions different, even though f and the lambda function are the same?

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

and

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

Mind you, both type(f) and type(lambda x: x*x) return the same type.


回答 0

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

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

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

或者更好:

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

The first one creates a single lambda function and calls it ten times.

The second one doesn’t call the function. It creates 10 different lambda functions. It puts all of those in a list. To make it equivalent to the first you need:

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

Or better yet:

[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变量。它没有任何跟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,但最好使用各自的版本…

This question touches a very stinking part of the “famous” and “obvious” Python syntax – what takes precedence, the lambda, or the for of list comprehension.

I don’t think the purpose of the OP was to generate a list of squares from 0 to 9. If that was the case, we could give even more solutions:

squares = []
for x in range(10): squares.append(x*x)
  • this is the good ol’ way of imperative syntax.

But it’s not the point. The point is W(hy)TF is this ambiguous expression so counter-intuitive? And I have an idiotic case for you at the end, so don’t dismiss my answer too early (I had it on a job interview).

So, the OP’s comprehension returned a list of lambdas:

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

This is of course just 10 different copies of the squaring function, see:

>>> [lambda x: x*x for _ in range(3)]
[<function <lambda> at 0x00000000023AD438>, <function <lambda> at 0x00000000023AD4A8>, <function <lambda> at 0x00000000023AD3C8>]

Note the memory addresses of the lambdas – they are all different!

You could of course have a more “optimal” (haha) version of this expression:

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

See? 3 time the same lambda.

Please note, that I used _ as the for variable. It has nothing to do with the x in the lambda (it is overshadowed lexically!). Get it?

I’m leaving out the discussion, why the syntax precedence is not so, that it all meant:

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

which could be: [[0, 1, 4, ..., 81]], or [(0, 1, 4, ..., 81)], or which I find most logical, this would be a list of 1 element – a generator returning the values. It is just not the case, the language doesn’t work this way.

BUT What, If…

What if you DON’T overshadow the for variable, AND use it in your lambdas???

Well, then crap happens. Look at this:

[lambda x: x * i for i in range(4)]

this means of course:

[(lambda x: x * i) for i in range(4)]

BUT it DOESN’T mean:

[(lambda x: x * 0), (lambda x: x * 1), ... (lambda x: x * 3)]

This is just crazy!

The lambdas in the list comprehension are a closure over the scope of this comprehension. A lexical closure, so they refer to the i via reference, and not its value when they were evaluated!

So, this expression:

[(lambda x: x * i) for i in range(4)]

IS roughly EQUIVALENT to:

[(lambda x: x * 3), (lambda x: x * 3), ... (lambda x: x * 3)]

I’m sure we could see more here using a python decompiler (by which I mean e.g. the dis module), but for Python-VM-agnostic discussion this is enough. So much for the job interview question.

Now, how to make a list of multiplier lambdas, which really multiply by consecutive integers? Well, similarly to the accepted answer, we need to break the direct tie to i by wrapping it in another lambda, which is getting called inside the list comprehension expression:

Before:

>>> a = [(lambda x: x * i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
2

After:

>>> a = [(lambda y: (lambda x: y * x))(i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
1

(I had the outer lambda variable also = i, but I decided this is the clearer solution – I introduced y so that we can all see which witch is which).

Edit 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.:

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

Great, but there is still a little twist for you! Let’s say we wan’t to make it easier on the code reader, and pass the factor by name (as a keyword argument to partial). Let’s do some renaming:

After 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'

Wait… We’re changing the number of arguments by 1, and going from “too many” to “too few”?

Well, it’s not a real WAT, when we pass coef to partial in this way, it becomes a keyword argument, so it must come after the positional x argument, like so:

After 3:

>>> a = [partial(lambda x, coef: coef * x, coef=i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

I would prefer the last version over the nested lambda, but to each their own…

Edit 2020-08-18:

Thanks to commenter dasWesen, I found out that this stuff is covered in the Python documentation: https://docs.python.org/3.4/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result – it deals with loops instead of list comprehensions, but the idea is the same – global or nonlocal variable access in the lambda function. There’s even a solution – using default argument values (like for any function):

>>> a = [lambda x, coef=i: coef * x for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

This way the coef value is bound to the value of i at the time of function definition (see James Powell’s talk “Top To Down, Left To Right”, which also explains why mutable default values are shunned).


回答 2

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

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

The big difference is that the first example actually invokes the lambda f(x), while the second example doesn’t.

Your first example is equivalent to [(lambda x: x*x)(x) for x in range(10)] while your second example is equivalent to [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,因此它将生成所有这些函数。

The first one

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

runs f() for each value in the range so it does f(x) for each value

the second one

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

runs the lambda for each value in the list, so it generates all of those functions.


回答 4

人们给出了很好的答案,但忘记了我认为最重要的部分:在第二个示例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]

People gave good answers but forgot to mention the most important part in my opinion: In the second example the X of the list comprehension is NOT the same as the X of the lambda function, they are totally unrelated. So the second example is actually the same as:

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

The internal iterations on range(10) are only responsible for creating 10 similar lambda functions in a list (10 separate functions but totally similar – returning the power 2 of each input).

On the other hand, the first example works totally different, because the X of the iterations DO interact with the results, for each iteration the value is X*X so the result would be [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()

The other answers are correct, but if you are trying to make a list of functions, each with a different parameter, that can be executed later, the following code will do that:

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]

While the example is contrived, I found it useful when I wanted a list of functions that each print something different, i.e.

import functools
a = [functools.partial(lambda x: print(x), x) for x in range(10)]

for i in a:
    i()

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。