【问题标题】:Why results of map() and list comprehension are different?为什么 map() 和列表理解的结果不同?
【发布时间】:2010-09-13 11:31:25
【问题描述】:

以下测试失败:

#!/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]

【问题讨论】:

标签: python closures list-comprehension late-binding generator-expression


【解决方案1】:

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

lambda : i

将始终返回值 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】:

    它们是不同的,因为生成器表达式和列表 comp 中的 i 的值都是惰性求值的,即在 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)
    

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

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

    f(*[lambda u=i: u for i in t])
    

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

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

    【讨论】:

    【解决方案3】:

    表达式f = lambda: i 等价于:

    def f():
        return i
    

    表达式g = lambda i=i: i 等价于:

    def g(i=i):
        return i
    

    i 在第一种情况下是 free variable,在第二种情况下它绑定到函数参数,即在这种情况下它是一个局部变量。默认参数的值在函数定义时进行评估。

    生成器表达式是lambda 表达式中i 名称的最近封闭范围(其中定义了i),因此i 在该块中解析:

    f(*(lambda: i for i in (1, -1)) # -> [-1, -1]
    

    ilambda i: ... 块的局部变量,因此它所引用的对象在该块中定义:

    f(*map(lambda i: lambda: i, (1,-1))) # -> [1, -1]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-04-07
      • 2015-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-29
      相关资源
      最近更新 更多