为什么map()和列表理解的结果不同?

jfs*_*jfs 11 python closures list-comprehension generator-expression late-binding

以下测试失败:

#!/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()
Run Code Online (Sandbox Code Playgroud)

换一种说法:

>>> 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]
Run Code Online (Sandbox Code Playgroud)

Tor*_*rek 9

它们是不同的,因为i生成器表达式和列表comp中的值都是懒惰地计算的,即在调用匿名函数时f.
到那时,i被绑定到最后一个值if t,即-1.

基本上,这就是列表理解所做的(同样对于genexp):

x = []
i = 1 # 1. from t
x.append(lambda: i)
i = -1 # 2. from t
x.append(lambda: i)
Run Code Online (Sandbox Code Playgroud)

现在lambdas带有一个引用的闭包i,但i在两种情况下都绑定为-1,因为这是它被分配给的最后一个值.

如果你想确保lambda收到当前值i,请执行

f(*[lambda u=i: u for i in t])
Run Code Online (Sandbox Code Playgroud)

这样,您i在创建闭包时强制进行评估.

编辑:生成器表达式和列表推导之间存在一个区别:后者将循环变量泄漏到周围的范围中.

  • @ S.Lott:Python中的普通函数并没有那么不同.`def f():返回i`你不知道`i`究竟是什么,无论是函数还是lambda都被认为是. (4认同)

Bri*_*ian 5

lambda捕获变量,而不是值,因此代码

lambda : i
Run Code Online (Sandbox Code Playgroud)

将始终返回我当前在闭包中绑定的值.调用它时,该值已设置为-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]
Run Code Online (Sandbox Code Playgroud)