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返回整个列表,其行为可能会有所不同…]