考虑此代码段:
>>> from itertools import chain
>>> foo = [0]
>>> for i in (1, 2):
... bar = (range(a, a+i) for a in foo)
... foo = chain(*list(bar))
...
>>> list(foo)
[0, 1]
这很有意义-在循环的第一次迭代中,bar
等效于iter([[0]])
,而foo的求值结果是chain([0])
,等效于iter([0])
。然后,在循环的第二次迭代中,bar
现在等于iter([[0, 1]])
,而foo变为iter([0, 1])
。这就是list(foo)
是[0, 1]
的原因。
[当我使用list(foo)
而不是foo = sum(list(bar), [])
时,对于chain(*list(bar))
也得到相同的结果。
现在考虑此代码段:
>>> from itertools import chain
>>> foo = [0]
>>> for i in (1, 2):
... bar = (range(a, a+i) for a in foo)
... foo = chain.from_iterable(bar)
...
>>> list(foo)
[0, 1, 1, 2]
您可以看到,唯一的区别是foo = chain.from_iterable(bar)
行,它使用itertools.chain.from_iterable
而不是itertools.chain.from_iterable
。
在我看来itertools.chain
大致等同于itertools.chain(*list(iterable))
,但是在这里情况并非如此。那么为什么最终结果会有所不同?
区别在于itertools.chain.from_iterable(iterable)
中的bar立即耗尽,而chain(*list(bar))
中的不是。在chain.from_iterable(bar)
的定义中,使用了bar
,这是后期绑定:它不是在定义时而是在评估时从名称i
中获取i
的值。
IOW,当您使用i
时,尚未评估foo = chain.from_iterable(bar)
。然后,当您调用bar
并“调用” list(foo)
时,定义中的bar
会选择名称i
当前指代-的值为2。]
因此,如果我们手动更改i
,我们应该能够适当地更改结果:
i
区别在于使用发电机和延迟评估>>> from itertools import chain
>>> foo = [0]
>>> for i in (1, 2):
... bar = (range(a, a+i) for a in foo)
... foo = chain.from_iterable(bar)
...
>>> i = 10
>>> list(foo)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
。如果将此行更改为foo = chain.from_iterable(bar)
,这两个程序将是等效的,这将强制对bar生成器的求值以具体值作为foo的基础。