简体   繁体   English

列表推导式中的 Lambda 函数

[英]Lambda function in list comprehensions

Why is the output of the following two list comprehensions different, even though f and the lambda function are the same?为什么以下两个列表推导式的输出不同,即使flambda函数相同?

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.请注意, type(f)type(lambda x: x*x)返回相同的类型。

The first one creates a single lambda function and calls it ten times.第一个创建一个 lambda 函数并调用它十次。

The second one doesn't call the function.第二个不调用该函数。 It creates 10 different lambda functions.它创建了 10 个不同的 lambda 函数。 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)]

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.这个问题触及了“著名”和“显而易见”的 Python 语法的一个非常糟糕的部分——什么优先,lambda 或列表理解的 for。

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:我不认为 OP 的目的是生成从 0 到 9 的方格列表。如果是这样,我们可以提供更多解决方案:

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?关键是 W(hy)TF 这个模棱两可的表达是否违反直觉? 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:因此,OP 的理解返回了一个 lambda 列表:

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

This is of course just 10 different copies of the squaring function, see:这当然只是平方函数的 10 个不同副本,请参阅:

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

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. 3 次相同的lambda。

Please note, that I used _ as the for variable.请注意,我使用_作为for变量。 It has nothing to do with the x in the lambda (it is overshadowed lexically!).它与lambdax无关(它在词汇上被掩盖了!)。 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.这可能是: [[0, 1, 4, ..., 81]][(0, 1, 4, ..., 81)] ,或者我认为最合乎逻辑的,这将是一个list 1 个元素 - 返回值的generator 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 lambda s???如果你不掩盖for变量,并在你的lambda s 中使用它怎么办???

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.列表推导式中的 lambda 表达式是该推导式范围的闭包。 A lexical closure, so they refer to the i via reference, and not its value when they were evaluated!一个词法闭包,所以它们通过引用引用i ,而不是它们被评估时的值!

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 eg the dis module), but for Python-VM-agnostic discussion this is enough.我相信我们可以在这里使用 python 反编译器看到更多内容(我的意思是例如dis模块),但是对于 Python-VM 不可知论的讨论,这已经足够了。 So much for the job interview question.面试问题到此为止。

Now, how to make a list of multiplier lambdas, which really multiply by consecutive integers?现在,如何制作一个真正乘以连续整数的乘法器 lambda list 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:那么,接受的答案一样,我们需要打破直接领带i在另一个包裹它lambda ,这是越来越称为列表解析表达式

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). (我也有外部 lambda 变量 = i ,但我认为这是更清晰的解决方案 - 我引入了y以便我们都可以看到哪个女巫是哪个)。

Edit 2019-08-30:编辑 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.遵循@josoler 的建议,该建议也出现在@sheridp 的回答中——列表推导式“循环变量”的值可以“嵌入”在对象中——关键是要在正确的时间访问它。 The section "After" above does it by wrapping it in another lambda and calling it immediately with the current value of i .上面的“After”部分通过将它包装在另一个lambda并立即使用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), ie:另一种方法(更容易阅读 - 它不会产生“WAT”效果)是将i的值存储在partial对象中,并让“内部”(原始) lambda将其作为参数(由调用时的partial对象),即:

After 2: 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 ).假设我们不想让代码阅读器更容易,并按名称传递因子(作为关键字参数给partial )。 Let's do some renaming:让我们做一些重命名:

After 2.5: 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"?等等...我们正在将参数的数量更改为 1,并从“太多”变为“太少”?

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:好吧,它不是真正的 WAT,当我们以这种方式将coef传递给partial时,它变成了关键字参数,因此它必须在位置x参数之后,如下所示:

After 3: 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...我更喜欢最后一个版本而不是嵌套的 lambda,但每个人都有自己的......

Edit 2020-08-18:编辑 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.感谢评论者 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 函数中的全局或非局部变量访问。 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).这样 coef 值在函数定义时绑定到 i 的值(参见 James Powell 的演讲“自上而下,从左到右”,这也解释了为什么要避开可变默认值)。

The big difference is that the first example actually invokes the lambda f(x) , while the second example doesn't.最大的区别是第一个示例实际上调用了 lambda f(x) ,而第二个示例没有。

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)] .您的第一个示例等效于[(lambda x: x*x)(x) for x in range(10)]而您的第二个示例等效于[f for x in range(10)]

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.人们给了很好的答案,但忘了提,我认为最重要的部分:在第二个例子中, X列表理解是不一样的X中的lambda功能,他们是完全无关的。 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). range(10)上的内部迭代仅负责在列表中创建 10 个相似的 lambda 函数(10 个单独的函数但完全相似 - 返回每个输入的 2 次幂)。

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]另一方面,第一个示例的工作方式完全不同,因为迭代的 X DO 与结果交互,对于每次迭代,值是X*X所以结果将是[0,1,4,9,16,25, 36, 49, 64 ,81]

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对范围内的每个值运行f()所以它对每个值执行f(x)

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.为列表中的每个值运行 lambda,因此它生成所有这些函数。

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, ie虽然这个例子是人为的,但当我想要一个函数列表时,我发现它很有用,每个函数都打印不同的东西,即

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