以下测试失败:
#!/usr/bin/env python
def f(*args):
"""
>>> t = 1, -1
>>> f(*map(lambda i: lambda: i, t))
[1, -1]
>>> f(*(lambda: i for i in t)) # -> [-1, -1]
[1, -1]
>>> f(*[lambda: i for i in t]) # -> [-1, -1]
[1, -1]
"""
alist = [a() for a in args]
print(alist)
if __name__ == '__main__':
import doctest; doctest.testmod()
换句话说:
>>> t = 1, -1
>>> args = []
>>> for i in t:
... args.append(lambda: i)
...
>>> map(lambda a: a(), args)
[-1, -1]
>>> args = []
>>> for i in t:
... args.append((lambda i: lambda: i)(i))
...
>>> map(lambda a: a(), args)
[1, -1]
>>> args = []
>>> for i in t:
... args.append(lambda i=i: i)
...
>>> map(lambda a: a(), args)
[1, -1]
答案 0 :(得分:9)
它们是不同的,因为生成器表达式和列表组合中i
的值都是懒惰地评估的,即在f
中调用匿名函数时。
到那时,i
绑定到最后一个值t
,即-1。
基本上,这就是列表理解所做的事情(同样对于genexp):
x = []
i = 1 # 1. from t
x.append(lambda: i)
i = -1 # 2. from t
x.append(lambda: i)
现在lambdas带有一个引用i
的闭包,但i
在两种情况下都绑定为-1,因为这是它被分配给的最后一个值。
如果要确保lambda收到当前值i
,请执行
f(*[lambda u=i: u for i in t])
这样,您在创建闭包时强制评估i
。
编辑:生成器表达式和列表推导之间存在一个区别:后者将循环变量泄漏到周围的范围内。
答案 1 :(得分:5)
lambda捕获变量,而不是值,因此代码
lambda : i
将始终返回在闭包中绑定的当前的值。调用它时,该值已设置为-1。
要获得你想要的东西,你需要在创建lambda时捕获实际的绑定,通过:
>>> f(*(lambda i=i: i for i in t)) # -> [-1, -1]
[1, -1]
>>> f(*[lambda i=i: i for i in t]) # -> [-1, -1]
[1, -1]
答案 2 :(得分:4)
表达式f = lambda: i
相当于:
def f():
return i
表达式g = lambda i=i: i
相当于:
def g(i=i):
return i
i
在第一种情况下是free variable,并且在第二种情况下绑定到函数参数,即在这种情况下它是局部变量。在函数定义时评估默认参数的值。
生成器表达式是i
表达式中i
名称的最近的封闭范围(其中lambda
已定义),因此在该块中解析i
:
f(*(lambda: i for i in (1, -1)) # -> [-1, -1]
i
是lambda i: ...
块的局部变量,因此它引用的对象在该块中定义:
f(*map(lambda i: lambda: i, (1,-1))) # -> [1, -1]