嵌套生成器表达式 - 意外结果
这是测试代码:
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 = ...
)形成了一个迭代器,我本来期待最后的结果能反映units
和tens
的最终赋值。另一方面,如果这个生成器表达式在第3行被计算,产生一个结果元组,那么我就期待使用units
和tens
的第一次定义。
但我看到的是一种混合的情况;也就是说,结果是[31, 41, 32, 42]
!?
有没有人能解释一下这种行为?
1 个回答
生成器表达式可以看作是一种“函数”,它只有一个参数,就是最外层的可迭代对象。
在这里,这个可迭代对象是 units
,当生成器表达式被创建时,它就被当作参数绑定了。
其他的名字要么是局部变量(比如 a
和 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
子句不能立即计算,因为它们可能依赖于前一个循环。例如:(x*y for x in range(10) for y in bar(x))
。
这个 PEP 中有一段很好的说明,解释了为什么(除了最外层的可迭代对象)其他名字会被延迟绑定,具体可以查看 早绑定与晚绑定。