复制一个itertools循环对象

3

我想制作一个itertools.cycle对象的浅层副本,但我不知道如何操作,因为它没有内置的复制方法。我想要实现以下效果,即创建循环的副本,迭代几次,然后再次复制原始副本,并从循环的开头再次迭代几次。

c = "ABCD"
cyc = itertools.cycle(c)

cyc_copy = cyc.copy()
for i in range(2):
    print(next(cyc_copy))
cyc_copy = cyc.copy()
for i in range(2):
    print(next(cyc_copy))

> A
  B
  A
  B

4
itertools.tee函数可以将一个可迭代对象复制多份,返回一个元组,其中每个元素都是原始可迭代对象的独立副本。这些副本可以分别迭代,而不会相互干扰或消耗原始可迭代对象。 - vaultah
itertools.tee可以工作,但它需要存储原始cycle迭代器的所有输出,包括循环回到自身时的重复输出。 - user2357112
3
你是否处于只能访问循环而不是原始可迭代对象的情况?似乎制作新的循环会是明智的选择。 - Alex Hall
5个回答

2
这里可能需要一些重构,但工厂模式可以很好地解决问题。
from itertools import cycle

cycle_factory = lambda: cycle('1234')

c1 = cycle_factory()
print next(c1) # 1

c2 = cycle_factory()
print next(c2) # 1

否则,我不确定您是否能够满足每次从周期开始的标准。基于类的方法也可以工作,但需要更多的开销。 itertools.tee 方法的一个问题是它将在 tee-d 迭代器离开的地方恢复迭代,而不是从头开始。因此,您必须在开头对其进行分叉。如果您无法控制循环如何生成,则这可能是唯一的选择。

1
复制循环本身会遇到问题。例如,复制它 copy.copy不会产生独立的副本
我建议从原始对象重新创建循环,而不是尝试复制它:
new_cyc = itertools.cycle(c)

如果您用于创建原始循环的对象是迭代器,则不能仅重复调用cycle。相反,在第一次调用cycle之前,请先创建一个列表并保留该列表:
c_list = list(c)
cyc = itertools.cycle(c_list)

# later
new_cyc = itertools.cycle(c_list)

如果您使用的原始循环对象是一个可能无限的迭代器,那么您不能安全地在其上调用list。相反,在创建循环之前,您可以先对其进行tee操作,然后在需要创建新循环时使用copy.copy复制未被推进的tee。(tee支持复制。)
c_tee, c_tee2 = itertools.tee(c)
cyc = itertools.cycle(c_tee2)

# Copy c_tee, not the c_tee2 we already used.
new_cyc = itertools.cycle(copy.copy(c_tee))

假设您控制循环的创建。如果您从其他地方接收循环,则可能无法访问其循环的对象。在这种情况下,您最好选择tee循环本身。如果您需要经过多次循环,则可能会很昂贵。
cyc_master, cyc1 = itertools.tee(cyc)
# Use cyc1

# Later
cyc2 = copy.copy(cyc_master)

1
你可以创建一个自定义类来实现你想要的功能:
import itertools

class CopyCycle:
    def __init__(self, iterable):
        self.iterable = iterable
        self._cycle = itertools.cycle(self.iterable)

    def cycle(self):
        return self

    def __iter__(self):
        return self

    def next(self):
        return self._cycle.next()

    def __next__(self):  #Python 3+
        return self._cycle.next()

    def copy(self):
        return CopyCycle(self.iterable)


if __name__ == '__main__':
    cyc = CopyCycle("ABCD").cycle()
    for i in range(5):
        print(next(cyc))

    cyc_copy = cyc.copy()
    for i in range(2):
        print(next(cyc_copy))
    cyc_copy = cyc.copy()
    for i in range(2):
        print(next(cyc_copy))

输出:

A
B
C
D
A
A
B
A
B

1
方法1:使用两个`itertools.cycle`对象。
import itertools
c = 'ABCD'
cyc1 = itertools.cycle(c)
cyc2 = itertools.cycle(c)
for _ in range(2): print(next(cyc1))  # prints A\nB\n
for _ in range(2): print(next(cyc2))  # prints A\nB\n

<script src="//repl.it/embed/IRcx/0.js"></script>

[首选解决方案] 方法2:使用itertools.tee将其分割成n个迭代器。

import itertools
cyc = itertools.cycle('ABCD')
cyc1, cyc2 = itertools.tee(cyc, 2)
for _ in range(2): print(next(cyc1))  # prints A\nB\n
for _ in range(2): print(next(cyc2))  # prints A\nB\n

<script src="//repl.it/embed/IRcx/2.js"></script>

使用copy模块时需要注意,使用copy.copy函数并不能按预期创建迭代器的副本。

import itertools, copy
cyc = itertools.cycle('ABCD')
cyc1 = copy.copy(cyc)
cyc2 = copy.copy(cyc)
for _ in range(2): print(next(cyc1))  # prints A\nB\n
for _ in range(2): print(next(cyc2))  # prints C\nD\n

<script src="//repl.it/embed/IRcx/3.js"></script>

使用copy模块的解决方法:可能的解决方案是使用copy.deepcopy函数。

import itertools, copy
cyc = itertools.cycle('ABCD')
cyc1 = copy.deepcopy(cyc)
cyc2 = copy.deepcopy(cyc)
for _ in range(2): print(next(cyc1))  # prints A\nB\n
for _ in range(2): print(next(cyc2))  # prints A\nB\n

<script src="//repl.it/embed/IRcx/4.js"></script>


0

copy 应该可以解决问题:

>>> from copy import copy
>>> cyc_copy = copy(cyc)
>>> next(cyc_copy)
'A'
>>> next(cyc_copy)
'B'
>>> cyc_copy = copy(cyc)
>>> next(cyc_copy)
'A'
>>> next(cyc_copy)
'B'

1
尝试在循环中调用copy会出现“TypeError: cycle expected 1 arguments, got 0”的错误。这是版本相关的吗?我正在使用Python 2.7.6。 - C_Z_
经过进一步测试,似乎与版本相关。Python 2会报“TypeError”错误。Python 3会给出一个不同的、更微妙的错误——循环不是独立的。它们共享某些基础状态,导致迭代其中一个会影响另一个。 - user2357112
我在Python 3.4上尝试了这个,似乎没有任何问题。 - Moses Koledoye

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接