以下代码将值转换为函数:
>>> a = map(lambda(x): lambda: x, [1, 2])
>>> [func() for func in a]
[1, 2]
但是以下代码段失败:
>>> a = [lambda: x for x in [1, 2]]
>>> [func() for func in a]
[2, 2]
这种异常是参数名称绑定神器吗?
这是关于范围的。函数定义了新的范围;列表推导的迭代,如for
循环和其他块语句,不会。
Python 编程常见问题解答为什么在循环中定义的具有不同值的 lambda 都返回相同的结果?在高层次上解释了这一点。我将尝试不同的解释,略过高层次,然后深入研究。
lambda: x
您的第一个版本正在调用为每个元素返回的函数。因为函数定义了新的作用域,每个这样的返回函数都有自己独立的x
.
您的第二个版本只是lambda: x
为每个元素定义一个。因为您在同一范围内执行所有这些操作,所以每个这样定义的函数都具有相同的x
. 事实上,因为x
是在全局范围内找到的,所以它x
是每个人都拥有的全局,正如您通过执行此操作所看到的:
>>> b = [lambda: x for x in [1, 2]]
>>> x = 20
>>> [func() for func in b]
[20, 20]
您可以通过定义和调用函数来解决此问题,使第二个版本等同于第一个版本,或者以通常的方式解决它,例如“默认参数破解”:
>>> c = [lambda x=x: x for x in [1, 2]]
>>> [func() for func in c]
[1, 2]
值得查看存储在函数对象中的内容以了解区别:
>>> a = map(lambda(x): lambda: x, [1, 2])
>>> [f.__closure__ for f in a]
[(<cell at 0x106523e50: int object at 0x7fb6a3c10298>,),
(<cell at 0x106523fa0: int object at 0x7fb6a3c10280>,)]
>>> [f.__code__.co_freevars for f in a]
(('x',), ('x',))
因此,在这里,每个函数都是一个带有单个单元格的闭包,每个单元格都命名为x
,但每个单元格都持有对不同int
对象的引用(值x
在每次循环中都绑定到)。
>>> b = [lambda: x for x in [1, 2]]
>>> [f.__closure__ for f in b]
[None, None]
>>> [f.__code__.co_freevars for f in b]
((), ())
>>> [f.__code__.co_names for f in b]
(('x',), ('x',))
所以这些根本不是闭包,只是引用全局变量的函数。
>>> c = [lambda x=x: x for x in [1, 2]]
>>> [f.__closure__ for f in b]
[None, None]
>>> [f.__code__.co_freevars for f in c]
((), ())
>>> [f.__code__.co_names for f in c]
((), ())
>>> [f.__code__.co_varnames for f in c]
(('x',), ('x',))
>>> [f.__defaults__ for f in c]
((1,), (2,))
在这里,没有闭包,也没有全局变量;我们有一个局部变量绑定到第一个参数,其默认值分别为 1 或 2。由于您在func
没有参数的情况下调用,因此您将获得默认值。
或者,您可以查看反汇编:
>>> dis.dis(a[0])
1 0 LOAD_DEREF 0 (x)
3 RETURN_VALUE
>>> dis.dis(b[0])
1 0 LOAD_GLOBAL 0 (x)
3 RETURN_VALUE
>>> dis.dis(c[0])
1 0 LOAD_FAST 0 (x)
3 RETURN_VALUE
但我怀疑有太多人知道 Python 字节码,但不知道函数的可检查值,所以……这可能没有太大帮助。
最后,如果您将函数定义函数移出线外,并使用def
而不是lambda
.
>>> def make_function(x):
... def function():
... return x
... return function
>>> a = map(make_function, [1, 2])
>>> b = [make_function(x) for x in [1, 2]]
现在两者a
和b
都在做同样的事情——调用一个返回函数的函数——没有什么可混淆的。