列表理解中的Lambdas在调用时返回lambda

时间:2016-07-14 08:43:47

标签: python lambda

我试图在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

3 个答案:

答案 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刚刚回答了问题中隐含的其他部分,所以我不会。