如何理解生成器中的作用域?
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)))
当 val
为 1
、2
和 3
时,对 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]]
同样,你必须考虑闭包值 当 它实际被评估时,在上面的例子中,因为我已经迭代了外部生成器,所以 i
是 2
,并简单地将内部生成器附加到另一个列表, 然后 我评估内部生成器,它们将看到 [=25 的值=] 为 2
,因为它就是这样。
重申一下,这是因为 *
splatting force 要迭代的生成器。使用 chain.from_iterable
代替,你会得到 True
作为你的 result1 == result2
.
我似乎误解了生成器中的变量作用域。为什么以下两个结果不同? (注意在生成第二个结果时第二次使用 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)))
当 val
为 1
、2
和 3
时,对 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]]
同样,你必须考虑闭包值 当 它实际被评估时,在上面的例子中,因为我已经迭代了外部生成器,所以 i
是 2
,并简单地将内部生成器附加到另一个列表, 然后 我评估内部生成器,它们将看到 [=25 的值=] 为 2
,因为它就是这样。
重申一下,这是因为 *
splatting force 要迭代的生成器。使用 chain.from_iterable
代替,你会得到 True
作为你的 result1 == result2
.