【问题标题】:Nested generator expression - unexpected result [duplicate]嵌套生成器表达式 - 意外结果 [重复]
【发布时间】:2014-05-06 19:44:05
【问题描述】:

这是测试代码:

units = [1, 2]
tens = [10, 20]
nums = (a + b for a in units for b in tens)
units = [3, 4]
tens = [30, 40]
[x for x in nums]

假设第 3 行 (nums = ...) 上的生成器表达式形成一个迭代器,我希望最终结果能够反映 unitstens 的最终分配值。 OTOH,如果要在第 3 行评估生成器表达式,生成结果元组,那么我希望使用 unitstens 的第一个定义。

我看到的是 MIX;即,结果是[31, 41, 32, 42]!?

谁能解释这种行为?

【问题讨论】:

  • 答案是一样的; units 是生成器表达式 'function' 的参数,而 tens 被查找为全局。所以units 绑定在第 3 行,tens 没有。
  • 请注意,这不是 Python 3 特定的。
  • @StevenRumbalski:不,它适用于从 2.4 开始的所有 Python 版本,其中引入了生成器表达式。
  • 我刚刚发现(来自给我发这个谜题的“朋友”)它来自web.archive.org/web/20111003161227/http://web.mit.edu/rwbarton/…(并在ballingt.com/2014/03/23/… 中引用)。我还不清楚适用的范围规则,但我会继续对这里提供的解释进行猛烈抨击,直到我弄明白为止。 (我想我更喜欢 Scheme 中的范围规则!)

标签: python python-3.x nested generator generator-expression


【解决方案1】:

生成器表达式创建一个函数;一个只有一个参数,最外层的可迭代。

这里是units,它在创建生成器表达式时作为参数绑定到生成器表达式。

所有其他名称要么是本地名称(例如 ab),要么是全局名称,要么是闭包。 tens 是作为全局查找的,因此每次推进生成器时都会查找它。

因此,units 在第 3 行绑定到生成器,tens 在最后一行迭代生成器表达式时被查找。

您可以在将生成器编译为字节码并检查该字节码时看到这一点:

>>> import dis
>>> genexp_bytecode = compile('(a + b for a in units for b in tens)', '<file>', 'single')
>>> dis.dis(genexp_bytecode)
  1           0 LOAD_CONST               0 (<code object <genexpr> at 0x10f013ae0, file "<file>", line 1>)
              3 LOAD_CONST               1 ('<genexpr>')
              6 MAKE_FUNCTION            0
              9 LOAD_NAME                0 (units)
             12 GET_ITER
             13 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             16 PRINT_EXPR
             17 LOAD_CONST               2 (None)
             20 RETURN_VALUE

MAKE_FUNCTION 字节码将生成器表达式代码对象转换为函数,并立即调用它,并传入iter(units) 作为参数。 tens 名称在这里根本没有被引用。

这记录在original generators PEP:

只有最外层的 for 表达式被立即计算,另一个 表达式会延迟到生成器运行:

g = (tgtexp  for var1 in exp1 if exp2 for var2 in exp3 if exp4)

相当于:

def __gen(bound_exp):
    for var1 in bound_exp:
        if exp2:
            for var2 in exp3:
                if exp4:
                    yield tgtexp
g = __gen(iter(exp1))
del __gen

generator expressions reference:

当为生成器对象调用__next__() 方法时,生成器表达式中使用的变量会被延迟计算(与普通生成器的方式相同)。但是,最左边的for 子句会立即被求值,因此可以在处理生成器表达式的代码中的任何其他可能错误之前看到由它产生的错误。后续的 for 子句不能立即计算,因为它们可能依赖于前一个 for 循环。例如:(x*y for x in range(10) for y in bar(x))

PEP 有一个很好的部分来激励为什么名称(除了最外层的可迭代对象)被绑定晚,请参阅Early Binding vs. Late Binding

【讨论】:

  • 你能指出这是在哪里记录的吗?是一种意想不到的行为。从反汇编代码中可以清楚地看到,但我想阅读它背后的逻辑。
  • @PauloBu:execution modelexpressions documentation 提示当然,生成器表达式以及列表、集合和字典推导使用单独的范围。
  • 这不是意外的行为。生成器引用 [10,20] 列表,因为它绑定到您在生成器表达式中使用的名称。然后将另一个列表 [30,40] 绑定到与生成器无关的名称。
  • 有趣。我对此感到惊讶。你知道tens 没有关闭的原因吗?有一种我在使用 Python 时不习惯的随意感。
  • @PauloBu:generator expression PEP 确实拼出来了。
猜你喜欢
  • 1970-01-01
  • 2019-06-12
  • 2020-02-16
  • 1970-01-01
  • 2017-05-08
  • 2020-07-07
  • 1970-01-01
  • 2019-02-10
  • 1970-01-01
相关资源
最近更新 更多