关于python:怪异的行为:列表理解内的Lambda

Weird behavior: Lambda inside list comprehension

在python 2.6中:

1
[x() for x in [lambda: m for m in [1,2,3]]]

结果是:

1
[3, 3, 3]

我希望输出为[1、2、3]。 即使使用非列表理解方法,我也会遇到完全相同的问题。 甚至在我将m复制到另一个变量之后。

我想念什么?


为了使Lambda记住m的值,可以使用具有默认值的参数:

1
2
[x() for x in [lambda m=m: m for m in [1,2,3]]]
# [1, 2, 3]

之所以可行,是因为默认值在定义时设置一次。现在,每个lambda都使用自己的默认值m,而不是在执行lambda时在外部范围中查找m的值。


您遇到的效果称为闭包,当您定义一个引用非局部变量的函数时,该函数将保留对该变量的引用,而不是获取其自己的副本。为了说明这一点,我将把您的代码扩展为等效版本,而无需任何理解或lambda。

1
2
3
4
5
inner_list = []
for m in [1, 2, 3]:
    def Lambda():
         return m
    inner_list.append(Lambda)

因此,此时,inner_list中具有三个函数,并且每个函数在被调用时将返回m的值。但是要注意的一点是,即使m发生了变化,他们都看到了完全相同的m,他们直到以后再调用时才看待它。

1
2
3
outer_list = []
for x in inner_list:
    outer_list.append(x())

特别是,由于内部列表是在外部列表开始构建之前完全构建的,因此m已经达到其最后一个值3,并且所有三个函数都看到相同的值。


长话短说,您不想这样做。更具体地说,您遇到的是操作顺序问题。您正在创建三个单独的lambda,它们都返回m,但是没有一个立即被调用。然后,当您到达外部列表理解时,它们都被称为m的残差值为3,即内部列表理解的最后一个值。

-评论-

1
2
>>> [lambda: m for m in range(3)]
[<function <lambda> at 0x021EA230>, <function <lambda> at 0x021EA1F0>, <function <lambda> at 0x021EA270>]

那是三个单独的lambda。

并且,作为进一步的证据:

1
2
>>> [id(m) for m in [lambda: m for m in range(3)]]
[35563248, 35563184, 35563312]

同样,三个单独的ID。


查看功能的__closure__。所有三个都指向同一个单元格对象,该对象从外部范围保留对m的引用:

1
2
3
4
5
>>> print(*[x.__closure__[0] for x in [lambda: m for m in [1,2,3]]], sep='
'
)
<cell at 0x00D17610: int object at 0x1E2139A8>
<cell at 0x00D17610: int object at 0x1E2139A8>
<cell at 0x00D17610: int object at 0x1E2139A8>

如果您不希望函数将m作为关键字参数,按照unubtu的回答,则可以在每次迭代时使用一个附加的lambda来评估m:

1
2
>>> [x() for x in [(lambda x: lambda: x)(m) for m in [1,2,3]]]
[1, 2, 3]

我个人认为这是一个更优雅的解决方案。 Lambda返回一个函数,因此如果我们要使用该函数,则应该使用它。在lambda中的"匿名"变量和生成器中使用相同的符号会造成混淆,因此在我的示例中,我使用了不同的符号以使其更清晰。

1
2
3
>>> [ (lambda a:a)(i) for i in range(3)]
[0, 1, 2]
>>>

它也更快。

1
2
3
4
5
>>> timeit.timeit('[(lambda a:a)(i) for i in range(10000)]',number=10000)
9.231263160705566
>>> timeit.timeit('[lambda a=i:a  for i in range(10000)]',number=10000)
11.117988109588623
>>>

但不如地图快:

1
2
>>> timeit.timeit('map(lambda a:a,  range(10000))',number=10000)
5.746963977813721

(我不止一次地运行了这些测试,结果是相同的,这是在python 2.7中完成的,在python 3中的结果是不同的:两个列表的理解在性能上更加接近,并且两者都慢得多,map仍然快得多。)


我也注意到了。我得出的结论是,lambda仅创建一次。因此,实际上,您的内部列表推导将给出3个与m的最后一个值都有关的相同函数。

试试看并检查元素的id()。

[注意:此答案不正确;查看评论]