itertools.chain.from_iterable的怪异行为

问题描述 投票:3回答:2

考虑此代码段:

>>> 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)),但是在这里情况并非如此。那么为什么最终结果会有所不同?

python itertools
2个回答
5
投票

区别在于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

0
投票

区别在于使用发电机和延迟评估>>> 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的基础。

© www.soinside.com 2019 - 2024. All rights reserved.