如何理解生成器中的作用域?

How to understand scope within generators?

我似乎误解了生成器中的变量作用域。为什么以下两个结果不同? (注意在生成第二个结果时第二次使用 tuple。)

def f(x): return x

result1 = tuple(itertools.chain(*((f(val) for _ in range(2)) for val in (1,2,3))))

result2 = tuple(itertools.chain(*(tuple(f(val) for _ in range(2)) for val in (1,2,3))))

print(result1==result2) # False; why?

从根本上说,示波器一直在正常工作。生成器只是创建一个局部的、封闭的范围,就像一个函数一样。本质上,您是在 val 上创建一个闭包,而在 Python 中,闭包是词法作用域和后期绑定的,即它们的值是在执行而不是定义时计算的。

两者之间的区别在于何时 外部生成器 get 与内部生成器迭代。在您的第一个示例中,外部生成器在任何内部生成器之前完全迭代,在第二个示例中,tuple 强制对它们进行串联评估。

问题是,当您使用 * 展开参数时,它 立即 评估您的生成器(外部生成器),但是,内部生成器未被评估yet,但它在 val 结束时关闭,但在第一个生成器结束时 val = 3

但是,在你的第二个例子中,

(tuple(f(val) for _ in range(2)) for val in (1,2,3)))

val123 时,对 tuple 的内部调用强制调用 f,并且因此,f 捕获了这些值。

因此,请考虑以下嵌套生成器,以及迭代它们的两种不同方式:

>>> def gen():
...     for i in range(3):
...         yield (i for _ in range(2))
...
>>> data = list(gen()) # essentially what you are doing with the splatting
>>> for item in data:
...     print(list(item))
...
[2, 2]
[2, 2]
[2, 2]
>>> for item in gen():
...     print(list(item))
...
[0, 0]
[1, 1]
[2, 2]
>>>

最后,这也应该提供信息:

>>> gs = []
>>> for item in gen():
...     gs.append(item)
...
>>> gs
[<generator object gen.<locals>.<genexpr> at 0x1041ceba0>, <generator object gen.<locals>.<genexpr> at 0x1041cecf0>, <generator object gen.<locals>.<genexpr> at 0x1041fc200>]
>>> [list(g) for g in gs]
[[2, 2], [2, 2], [2, 2]]

同样,你必须考虑闭包值 它实际被评估时,在上面的例子中,因为我已经迭代了外部生成器,所以 i2,并简单地将内部生成器附加到另一个列表, 然后 我评估内部生成器,它们将看到 [=25 的值=] 为 2,因为它就是这样。

重申一下,这是因为 * splatting force 要迭代的生成器。使用 chain.from_iterable 代替,你会得到 True 作为你的 result1 == result2.