如何防止迭代器用尽?

问题描述

无法“重置”生成器。但是,您 可以 使用itertools.tee“复制”迭代器。

>>> z = zip(a, b)
>>> zip1, zip2 = itertools.tee(z)
>>> list(zip1)
[(1, 7), (2, 8), (3, 9)]
>>> list(zip2)
[(1, 7), (2, 8), (3, 9)]

这涉及到缓存值,因此仅当您以大约相同的速率遍历两个可迭代对象时才有意义。(换句话说,不要像我在这里那样使用它!)

另一种方法是传递生成函数,并在需要迭代时调用它。

def gen(x):
    for i in range(x):
        yield i ** 2

def make_two_lists(gen):
    return list(gen()), list(gen())

但是现在,您必须在传递函数时将参数绑定到生成函数。您可以使用lambda它,但是很多人都觉得lambda丑陋。(虽然不是我!YMMV。)

>>> make_two_lists(lambda: gen(10))
([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], [0, 1, 4, 9, 16, 25, 36, 49, 64, 81])

我希望不用多说,在大多数情况下,最好列出并复制列表。

另外,作为解释此行为的更一般方法,请考虑一下。生成器的作用是生成一系列值,同时在迭代之间保持某种状态。现在,有时,您可能不希望简单地在生成器上进行迭代,而是要执行以下操作:

z = zip(a, b)
while some_condition():
    fst = next(z, None)
    snd = next(z, None)
    do_some_things(fst, snd)
    if fst is None and snd is None:
        do_some_other_things()

假设此循环 可能会可能不会 耗尽z。现在,我们的发电机处于不确定状态!因此,在这一点上,必须以明确定义的方式限制生成器的行为。尽管我们不知道生成器在其输出中的位置,但我们知道a)所有后续访问都会在该系列中产生更高的 值,并且b)一旦它为“空”,我们就可以准确地获得该系列中的所有项目一旦。我们必须具有越多的能力来操纵的状态,就越z难以对此进行推理,因此最好避免出现违反这两个诺言的情况。

当然,正如乔尔·科内特(Joel Cornett)在下面指出的那样, 可能编写一个通过该send方法接受消息的生成器。并且可以编写可以使用重置的生成send。但是请注意,在这种情况下,我们所能做的就是发送一条消息 。我们不能直接操纵生成器的状态,因此对生成器状态的所有更改都是定义明确的(通过生成器本身- 假设其编写正确!)。send确实是实现协程的,所以我不会将其用于此目的。每天生成器几乎永远不会对发送给它们的值做任何事情- 我认为出于上述原因。

解决方法

如果我创建两个列表并将其压缩

a=[1,2,3]
b=[7,8,9]
z=zip(a,b)

然后我将z输入到两个列表中

l1=list(z)
l2=list(z)

那么,l1的内容就很好了[(1,7),(2,8),(3,9)],但是l2的内容只是[]。

我猜想这是python关于可迭代项的一般行为。但是,作为从C家族迁移的新手程序员,这对我来说没有意义。为什么会这样表现?有办法解决这个问题吗?

我的意思是,是的,在这个特定示例中,我可以将l1复制到l2中,但是总的来说,在迭代一次之后,是否有一种方法可以“重置” Python用于迭代“ z”的内容?