这个问题涉及到“著名”和“显而易见”的 Python 语法中一个非常臭的部分——什么是优先的、lambda 或 for of list comprehension。
我不认为 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 的内存地址 - 它们都是不同的!
你当然可以有这个表达式的更“最佳”(哈哈)版本:
>>> [lambda x: x*x] * 3
[<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]
看到了吗? 3 次相同 lambda。
请注意,我使用_ 作为for 变量。它与lambda 中的x 无关(它在词汇上被掩盖了!)。明白了吗?
我不讨论了,为什么语法优先级不是这样,这一切都意味着:
[lambda x: (x*x for x in range(10))]
可能是:[[0, 1, 4, ..., 81]],或[(0, 1, 4, ..., 81)],或我认为最合乎逻辑的,这将是 1 个元素的 list - 返回值的 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 是此推导范围的闭包。 lexical 闭包,因此它们通过引用引用 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 的 list,它真正乘以连续整数?好吧,与公认的答案类似,我们需要打破与i 的直接联系,方法是将其包装在另一个lambda 中,该lambda 被称为inside列表理解表达式:
之前:
>>> 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 的回答中也有该建议 - 列表推导“循环变量”的值可以“嵌入”到对象中 - 关键是它可以在合适的时间。上面的“After”部分通过将其包装在另一个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'
什么?
>>> 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 的演讲“从上到下,从左到右”,这也解释了为什么回避可变默认值)。