奇怪的行为: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()。
[注意:这个答案不正确; 看评论]