在python 2.6中:
[x() for x in [lambda: m for m in [1,2,3]]]
结果:
[3, 3, 3]
我希望输出为[1,2,3]。即使使用非列表理解方法,我也会得到完全相同的问题。甚至在我将m复制到另一个变量之后。
我错过了什么?
答案 0 :(得分:15)
要使lambdas记住m
的值,您可以使用带有默认值的参数:
[x() for x in [lambda m=m: m for m in [1,2,3]]]
# [1, 2, 3]
这是有效的,因为默认值在定义时设置一次。现在,每个lambda都使用自己的默认值m
,而不是在lambda执行时在外部作用域中查找m
的值。
答案 1 :(得分:6)
您遇到的效果称为closures,当您定义一个引用非局部变量的函数时,该函数会保留对该变量的引用,而不是获取自己的副本。为了说明,我将你的代码扩展为一个没有理解或lambdas的等效版本。
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())
特别是,由于内部列表是在外部列表开始构建之前完全构造的,因此m
已经达到其最后一个值3,并且所有三个函数都看到相同的值。
答案 2 :(得分:5)
长话短说,你不想这样做。更具体地说,您遇到的是操作顺序问题。您正在创建三个单独的lambda
,它们全部返回m
,但不会立即调用它们。然后,当你到达外部列表理解并且它们全部被称为m
的剩余值是3时,内部列表理解的最后一个值。
- 征求意见 -
>>> [lambda: m for m in range(3)]
[<function <lambda> at 0x021EA230>, <function <lambda> at 0x021EA1F0>, <function <lambda> at 0x021EA270>]
这是三个独立的羔羊。
并且,作为进一步的证据:
>>> [id(m) for m in [lambda: m for m in range(3)]]
[35563248, 35563184, 35563312]
再次,三个单独的ID。
答案 3 :(得分:3)
查看函数的__closure__
。所有3指向同一个单元格对象,它保留对外部范围的引用:
>>> 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]
答案 4 :(得分:0)
就个人而言,我发现这是一个更优雅的解决方案。 Lambda返回一个函数,所以如果我们想使用该函数,那么我们应该使用它。对于匿名用户使用相同的符号会让人感到困惑。在lambda和生成器中的变量,所以在我的例子中,我使用一个不同的符号,使它更有希望。
>>> [ (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
(我不止一次运行这些测试,结果是一样的,这是在python 2.7中完成的,结果在python 3中有所不同:两个列表理解在性能上更接近并且两者都慢得多,地图仍然快得多。)
答案 5 :(得分:0)
@unubtu的答案是正确的。我在带有封闭的Groovy中重新创建了场景。也许可以说明正在发生的事情。
这类似于[x() for x in [lambda: m for m in [1,2,3]]]
arr = []
x = 0
while (x < 3) {
x++
arr.add({ -> x })
}
arr.collect { f -> f() } == [3, 3, 3]
这类似于[x() for x in [lambda m=m: m for m in [1,2,3]]]
arr = []
x = 0
while (x < 3) {
x++
arr.add({_x -> { -> _x }}(x))
}
arr.collect { f -> f() } == [1, 2, 3]
请注意,如果我使用[1,2,3].each {x -> ... }
而不是while循环,则不会发生这种情况。 Groovy while循环和Python列表理解都在迭代之间共享其闭包。
答案 6 :(得分:-1)
我也注意到了。我断定lambda只创建一次。所以实际上你的内部列表理解将给出3个与m的最后一个值相关的缩进函数。
尝试并检查元素的id()。
[注意:这个答案不正确;见评论]