复制链

时间:2017-03-23 21:30:58

标签: python copy itertools

当我copy.copyitertools.chain

时,我注意到了一些奇怪的行为
from copy import copy
from itertools import chain

当我耗尽其中一个时,结果如预期:

>>> a = chain([1,2,3], [4,5,6])
>>> b = copy(a)
>>> list(a), list(b)
([1, 2, 3, 4, 5, 6], [])

>>> a, b = chain_and_copy()
>>> list(b), list(a)
([1, 2, 3, 4, 5, 6], [])

然而,当我使用next时,结果似乎很奇怪:

>>> a = chain([1,2,3], [4,5,6])
>>> b = copy(a)
>>> next(a), list(b), list(a)
(1, [4, 5, 6], [2, 3])   # b "jumps" to the second iterable...

>>> a = chain([1,2,3], [4,5,6])
>>> next(a)
1
>>> b = copy(a)
>>> next(a), next(b), next(a)
(2, 3, 4)
>>> next(b)   # b is empty
StopIteration:
>>> next(a)   # a is not empty
5

这是一个Bug还是浅层复制迭代器通常是一个坏主意?我注意到iter的副本和zip的副本的行为也不同:

>>> a = zip([1,2,3], [4,5,6])
>>> b = copy(a)
>>> next(a), next(b)
((1, 4), (2, 5))  # copies share the same "position"

>>> a = iter([1,2,3])
>>> b = copy(a)
>>> next(a), next(b)
(1, 1)   # copies don't share the same "position"

1 个答案:

答案 0 :(得分:2)

您只是因为使用嵌套的iterables和简单的iterables而感到困惑。

关于copy和您的第一个示例,您只需使用deepcopy来创建可迭代的正确副本:

In [87]: a = chain([1,2,3], [4,5,6])

In [88]: b = deepcopy(a)

In [89]: list(a)
Out[89]: [1, 2, 3, 4, 5, 6]

In [90]: list(b)
Out[90]: [1, 2, 3, 4, 5, 6]

next也没有特别之处。这相当于python文档中的chain函数:

def chain(*iterables):
    # chain('ABC', 'DEF') --> A B C D E F
    for it in iterables:
        for element in it:
            yield element

正如您所看到的,第一个for循环遍历迭代,在这种情况下是[1,2,3][4,5,6]所以如果你只是复制生成器对象并实际创建一个浅拷贝对它来说,首先对next的每次调用都将使用其中一个迭代,然后迭代迭代项。因此,当您致电next(a)时,它已经消耗了第一个可迭代,以及list(b)返回[4, 5, 6]的原因。

如果你使用deepcopy,你又不会再看到这种行为了。

In [94]: a = chain([1,2,3], [4,5,6])

In [95]: b = deepcopy(a)

In [96]: next(a), list(b), list(a)
Out[96]: (1, [1, 2, 3, 4, 5, 6], [2, 3, 4, 5, 6])

对于zip也是如此,因为您将多个iterable传递给函数。如果您使用deepcopy,您最终会得到不同的对象:

In [100]: a = zip([1,2,3], [4,5,6])

In [101]: b = deepcopy(a)

In [102]: next(a), next(b)
Out[102]: ((1, 4), (1, 4))

但是copy适用于iter,因为您只是将一个迭代传递给该函数,而不需要deepcopy

毕竟,复制生成器的最好(最pythonic)方法是使用itertools.tee

In [103]: from itertools import tee

In [104]: a = zip([1,2,3], [4,5,6])

In [105]: a, b = tee(a)

In [106]: list(a)
Out[106]: [(1, 4), (2, 5), (3, 6)]

In [107]: list(b)
Out[107]: [(1, 4), (2, 5), (3, 6)]

In [108]: 

In [108]: a = chain([1,2,3], [4,5,6])

In [109]: a, b = tee(a)

In [110]: list(a)
Out[110]: [1, 2, 3, 4, 5, 6]

In [111]: list(b)
Out[111]: [1, 2, 3, 4, 5, 6]