奇怪的行为:Lambda在列表理解中

在python 2.6中:

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

结果是:

 [3, 3, 3] 

我预计输出是[1,2,3]。 即使使用非列表理解方法,我也会遇到同样的问题。 甚至在我将m复制到另一个variables之后。

我错过了什么?

为了让lambdaexpression式记住m的值,可以使用一个具有默认值的参数:

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

这是有效的,因为默认值在定义时间被设置一次。 每个lambda现在都使用它自己的默认值m而不是在lambda执行时在外部范围中查找m的值。

您遇到的效果称为闭包 ,当您定义一个引用非局部variables的函数时,该函数将保留对variables的引用,而不是获取自己的副本。 为了说明,我将把你的代码扩展到一个没有理解或lambdaexpression式的同等版本。

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

所以,在这一点上, inner_list有三个函数,每个函数调用的时候都会返回m的值。 但最重要的是,他们都看到了同样的m ,即使m在变化,他们从来没有看过它,直到后来被调用。

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

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

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

– 评论 –

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

这是三个单独的lambda。

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

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

再次,三个单独的ID。

看看函数的__closure__ 。 所有3个指向相同的单元格对象,它从外部作用域保持对m的引用:

 >>> print(*[x.__closure__[0] for x in [lambda: m for m in [1,2,3]]], sep='\n') <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:

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

就个人而言,我觉得这是一个更优雅的解决scheme。 Lambda返回一个函数,所以如果我们想使用函数,那么我们应该使用它。 在lambda和generator中使用相同的符号来表示'anonymous'variables,这是令人困惑的,所以在我的例子中,我使用了一个不同的符号来使它更加清晰。

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

它也更快。

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

但不如地图快:

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

(我不止一次运行了这些testing,结果是一样的,这是在python 2.7中完成的,python 3中的结果是不同的:两个列表parsing在性能上相差很多,而且速度慢得多,map要快得多。

我也注意到了。 我的结论是,lambda只创build一次。 所以实际上你的内部列表理解将会给出所有与m的最后一个值有关的3个相同的函数。

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

[注意:这个答案不正确; 看评论]