我试图在test.py
中的列表上迭代lambda func,我想得到lambda的调用结果,而不是函数对象本身。但是,以下输出真的让我很困惑。
------test.py---------
#!/bin/env python
#coding: utf-8
a = [lambda: i for i in range(5)]
for i in a:
print i()
--------output---------
<function <lambda> at 0x7f489e542e60>
<function <lambda> at 0x7f489e542ed8>
<function <lambda> at 0x7f489e542f50>
<function <lambda> at 0x7f489e54a050>
<function <lambda> at 0x7f489e54a0c8>
我将调用结果打印到t
时修改了变量名,如下所示,一切顺利。我想知道这是怎么回事。 ?
--------test.py(update)--------
a = [lambda: i for i in range(5)]
for t in a:
print t()
-----------output-------------
4
4
4
4
4
答案 0 :(得分:47)
在Python 2中,列表理解将变量“泄漏”到外部范围:
>>> [i for i in xrange(3)]
[0, 1, 2]
>>> i
2
请注意,Python 3上的行为是不同的:
>>> [i for i in range(3)]
[0, 1, 2]
>>> i
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'i' is not defined
当你定义lambda时,它绑定到变量i
,而不是它的第二个例子显示的当前值。
现在,当您为i
分配新值时,lambda将返回当前值:
>>> a = [lambda: i for i in range(5)]
>>> a[0]()
4
>>> i = 'foobar'
>>> a[0]()
'foobar'
由于循环中i
的值是lambda本身,你将把它作为返回值:
>>> i = a[0]
>>> i()
<function <lambda> at 0x01D689F0>
>>> i()()()()
<function <lambda> at 0x01D689F0>
更新:Python 2.7上的示例:
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [lambda: i for i in range(5)]
>>> for i in a:
... print i()
...
<function <lambda> at 0x7f1eae7f15f0>
<function <lambda> at 0x7f1eae7f1668>
<function <lambda> at 0x7f1eae7f16e0>
<function <lambda> at 0x7f1eae7f1758>
<function <lambda> at 0x7f1eae7f17d0>
相同的Python 3.4:
Python 3.4.3 (default, Oct 14 2015, 20:28:29)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [lambda: i for i in range(5)]
>>> for i in a:
... print(i())
...
4
4
4
4
4
有关具有列表推导的变量范围变化的详细信息,请参阅Guido的blogpost from 2010。
我们还在Python 3中进行了另一项更改,以改进列表推导和生成器表达式之间的等效性。在Python 2中,列表推导将循环控制变量“泄漏”到周围的范围中:
x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'
然而,在Python 3中,我们决定使用与生成器表达式相同的实现策略来修复列表推导的“脏小秘密”。因此,在Python 3中,上面的例子(修改后使用print(x):-)将打印'before',证明列表理解中的'x'暂时阴影但不覆盖周围的'x'范围。
答案 1 :(得分:19)
Python中的闭包是late-binding,这意味着列表中的每个lambda函数只会在调用时评估变量i
,并且在定义时不评估 。这就是为什么所有函数都返回相同的值,即ì
的最后一个值(即4)。
为避免这种情况,一种方法是将i
的值绑定到本地命名参数:
>>> a = [lambda i=i: i for i in range(5)]
>>> for t in a:
... print t()
...
0
1
2
3
4
另一种选择是创建partial function并将i
的当前值绑定为其参数:
>>> from functools import partial
>>> a = [partial(lambda x: x, i) for i in range(5)]
>>> for t in a:
... print t()
...
0
1
2
3
4
编辑抱歉,最初误读了这个问题,因为这类问题经常与后期绑定有关(感谢@soon评论)。
行为的第二个原因是列表理解的变量在Python2中泄漏,正如其他人已经解释过的那样。在i
循环中使用for
作为迭代变量时,每个函数都会打印当前值i
(由于上述原因),这只是函数本身。
当使用其他名称(例如t
)时,函数会打印i
的最后一个值,就像它在列表推导循环中一样,即4。
答案 2 :(得分:4)
lambda: i
是一个匿名函数,没有返回i的参数。因此,您将生成一个匿名函数列表,您可以稍后(在第二个示例中)绑定到名称t
并使用()
调用。请注意,您可以对非匿名函数执行相同的操作:
>>> def f():
... return 42
...
>>> name = f
>>> name
<function f at 0x7fed4d70fb90>
>>> name()
42
@plamut刚刚回答了问题中隐含的其他部分,所以我不会。