【问题标题】:Generator expression in list comprehension not working as expected列表理解中的生成器表达式未按预期工作
【发布时间】:2018-10-13 07:19:13
【问题描述】:

以下代码产生预期的输出:

# using a list comprehension as the first expression to a list comprehension
>>> l = [[i*2+x for x in j] for i,j in zip([0,1],[range(4),range(4)])]
>>> l[0]
[0, 1, 2, 3]
>>> l[1]
[2, 3, 4, 5]

但是,当我改用生成器表达式时,我得到了不同的结果:

# using a generator expression as the first expression
>>> l = [(i*2+x for x in j) for i,j in zip([0,1],[range(4),range(4)])]
>>> list(l[0])
[2, 3, 4, 5]
>>> list(l[1])
[2, 3, 4, 5]
>>> list(l[0])
[]
>>> list(l[1])
[]
>>> l
[<generator object <listcomp>.<genexpr> at 0x7fddfa413ca8>, <generator object <listcomp>.<genexpr> at 0x7fddfa413c50>]

我知道生成器表达式只能使用一次,但是我无法推理为什么在这种情况下我会两次获得相同的列表,尤其是因为生成器对象似乎是唯一的。

我在这里缺少什么?这是在 Python 3.6.5 上测试的。

【问题讨论】:

    标签: python python-3.x list-comprehension generator-expression


    【解决方案1】:

    因为在每个生成器表达式执行i 绑定为1。生成器表达式不会捕获它们在创建时生效的绑定 - 它们使用在执行时生效的绑定。

    >>> j = 100000
    >>> e = (j for i in range(3))
    >>> j = -6
    >>> list(e)
    [-6, -6, -6]
    

    【讨论】:

      【解决方案2】:

      生成器对象是唯一的,但它们引用ij,但列表理解终止(这实际上创建了一个函数范围,就像列表理解中的生成器表达式一样)。因此,ij 具有值 i == 1j == range(4)。你甚至可以反省一下:

      In [1]: l = [(i*2+x for x in j) for i,j in zip([0,1],[range(4),range(4)])]
      
      In [2]: g = l[0]
      
      In [3]: g.gi_frame.f_locals
      Out[3]: {'.0': <range_iterator at 0x10e9be960>, 'i': 1}
      

      这与经常出现这种令人惊讶的行为的原因基本相同:

      In [4]: fs = [lambda: i for i in range(3)]
      
      In [5]: fs[0]
      Out[5]: <function __main__.<listcomp>.<lambda>()>
      
      In [6]: fs[0]()
      Out[6]: 2
      
      In [7]: fs[1]()
      Out[7]: 2
      
      In [8]: fs[2]()
      Out[8]: 2
      

      您可以使用相同的解决方案来解决此问题,即创建另一个封闭范围,它将本地变量绑定到不会改变的东西。使用一个函数(这里是一个 lambda,但它可以是常规函数)会很好地工作:

      In [9]: l = [(lambda i, j: (i*2+x for x in j))(i, j) for i,j in zip([0,1],[range(4),range(4)])]
      
      In [10]: list(l[0])
      Out[10]: [0, 1, 2, 3]
      
      In [11]: list(l[1])
      Out[11]: [2, 3, 4, 5]
      

      虽然,也许为了清楚起见,我将使用不同的参数名称以使发生的事情更加明显:

      In [12]: l = [(lambda a, b: (a*2+x for x in b))(i, j) for i,j in zip([0,1],[range(4),range(4)])]
      
      In [13]: list(l[0])
      Out[13]: [0, 1, 2, 3]
      
      In [14]: list(l[1])
      Out[14]: [2, 3, 4, 5]
      

      【讨论】:

      • 有道理;我假设它自动实现了某种类型的闭包。我喜欢通过 lambda 强制执行新范围的想法
      • @MikeLui 它确实创建了一个闭包。本质上,生成器表达式使用函数范围。这与列表理解中的 lambda:i (创建闭包)总是返回 2 的原因相同。
      猜你喜欢
      • 1970-01-01
      • 2020-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-28
      • 2016-01-18
      相关资源
      最近更新 更多