Python itertools.chain.from_iterable的怪异行为

Python itertools.chain.from_iterable的怪异行为,python,itertools,Python,Itertools,考虑以下代码片段: >>> 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] >>> from itertools import c

考虑以下代码片段:

>>> 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]
>>> 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]
这是有意义的——在循环的第一次迭代中,
bar
相当于
iter([[0]])
,foo的计算结果是
chain([0])
,这相当于
iter([0])
。然后,在循环的第二次迭代中,
bar
现在相当于
iter([[0,1]])
,foo变成
iter([0,1])
。这就是为什么
list(foo)
[0,1]

当我使用
foo=sum(list(bar),[])
而不是
chain(*list(bar))
时,
list(foo)
也会得到相同的结果

现在考虑这个代码片段:

>>> 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]
>>> 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\u iterable(bar)
行,它使用而不是
itertools.chain


在我看来,
itertools.chain(*list(iterable))
大致相当于
itertools.chain.from\u iterable(iterable)
,但这里的情况并非如此。那么,为什么最终结果不同呢?

不同之处在于,在
链(*list(bar))
中,bar会立即耗尽,而在
链中。从iterable(bar)
中,则不会。在
bar
的定义中,使用了
i
,这是后期绑定:它不是在定义时提取
i
的值,而是在计算时从名称
i
中提取

注意,当您使用
foo=chain.from_iterable(bar)
时,
bar
尚未计算。然后,当您调用
list(foo)
,它“调用”
bar
,定义中的
i
将拾取名称
i
当前引用的值,即2

因此,如果我们手动更改
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)
。如果您将此行更改为
foo=chain.from_iterable(list(bar))
,则这两个程序将是等效的,这将强制对条形生成器进行求值,使foo以具体值表示

否则,正如所写的,这两个程序在语义上是不同的,前者将链应用于列表,而第二个将链应用于生成器,在某些方面可以将其视为函数句柄,这将延迟执行,直到循环完成后调用最终的
list(foo)

[这个答案在Python3中测试过,其中range是一个生成器。在Python2.x中,range返回整个列表,其行为可能会有所不同…]