以下代码将值转换为函数:
>>> 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]
这种像差是参数名称绑定工件吗?
答案 0 :(得分:5)
这是关于范围的。函数定义新范围;列表推导的迭代,如for
循环和其他块语句,不会。
Python Programming FAQ问题Why do lambdas defined in a loop with different values all return the same result?在很高的层次上解释了这一点。我将尝试不同的解释,略读高层,然后深入潜水。
您的第一个版本正在调用一个函数,该函数为每个元素返回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]
您可以通过定义和调用函数来修复此问题,使第二个版本等同于第一个版本,或者以通常的方式解决它,例如“默认参数hack”:
>>> 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
正在做同样的事情 - 调用一个返回函数的函数 - 并且没有什么可以混淆的。