繁体   English   中英

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

[英]Lambdas from a list comprehension are returning a lambda when called

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

在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

有关列表理解的变量范围变化的详细信息,请参阅2010年的 Guido 博客文章

我们还在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'范围。

Python中的闭包是后期绑定的 ,这意味着列表中的每个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

另一种选择是创建一个部分函数并将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。

lambda: i是一个匿名函数,没有返回i的参数。 因此,您将生成一个匿名函数列表,您可以稍后(在第二个示例中)绑定到名称t并使用()调用。 请注意,您可以对非匿名函数执行相同的操作:

>>> def f():
...   return 42
... 
>>> name = f
>>> name
<function f at 0x7fed4d70fb90>
>>> name()
42

@plamut刚刚回答了问题中隐含的其他部分,所以我不会。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM