为什么map()和列表理解的结果不同?
时间:2020-03-06 14:47:12 来源:igfitidea点击:
以下测试失败:
#!/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]
解决方案
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]
它们是不同的,因为生成器表达式和list comp中的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)
现在,lambda包含一个引用" i"的闭包,但是在两种情况下" i"都绑定为-1,因为这是它被分配给的最后一个值。
如果要确保lambda接收到当前值" i",请执行
f(*[lambda u=i: u for i in t])
这样,我们可以在创建闭包时强制执行对i的求值。
编辑:生成器表达式和列表推导之间有一个区别:后者将循环变量泄漏到周围的作用域中。
表达式" f = lambda:i"等效于:
def f(): return i
表达式" g = lambda i = i:i"等效于:
def g(i=i): return i
在第一种情况下," i"是一个自由变量,在第二种情况下,它被绑定到函数参数,即在这种情况下,它是一个局部变量。默认参数的值在函数定义时进行评估。
生成器表达式是lambda表达式中i名称最接近的包围范围(其中定义了i),因此在该块中解析了i:
f(*(lambda: i for i in (1, -1)) # -> [-1, -1]
i是lambda i:...块的局部变量,因此在该块中定义了它所引用的对象:
f(*map(lambda i: lambda: i, (1,-1))) # -> [1, -1]