将值转换为函数

时间:2013-08-03 00:35:19

标签: python binding lambda

以下代码将值转换为函数:

>>> 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]

这种像差是参数名称绑定工件吗?

1 个答案:

答案 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]]

现在ab正在做同样的事情 - 调用一个返回函数的函数 - 并且没有什么可以混淆的。