1

这是测试代码:

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 行评估生成器表达式,生成结果元组,那么我希望使用unitsand的第一个定义tens

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

谁能解释这种行为?

4

1 回答 1

3

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

这里是units,并且在创建生成器表达式时将其绑定为生成器表达式的参数。

所有其他名称要么是局部变量(例如aand b)、全局变量或闭包。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这里根本没有提到这个名字。

这记录在原始生成器 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

并在生成器表达式参考中

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

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

于 2014-03-27T17:06:43.643 回答