嵌套生成器表达式 - 意外结果

时间:2014-03-27 17:00:29

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

这是测试代码:

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]!?

任何人都可以解释这种行为吗?

1 个答案:

答案 0 :(得分:3)

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

这里是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-expression被立即评估,另一个   表达式将延迟到生成器运行:

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