python 为什么我不能在同一个迭代器上迭代两次?我如何“重置”迭代器或重用数据?

bhmjp9jg  于 2023-01-08  发布在  Python
关注(0)|答案(5)|浏览(534)

考虑代码:

def test(data):
    for row in data:
        print("first loop")
    for row in data:
        print("second loop")

data是一个迭代器时,例如列表迭代器或生成器表达式 *,这是不起作用的:

>>> test(iter([1, 2]))
first loop
first loop
>>> test((_ for _ in [1, 2]))
first loop
first loop

这会打印first loop几次,因为data不是空的。但是,它 * 不 * 打印second loop为什么在data上迭代第一次可以工作,但第二次不行?我怎样才能让它第二次工作?
除了for循环之外,任何类型的迭代都会出现同样的问题:列表/集合/字典解析,将迭代器传递给list()sum()reduce()等。
另一方面,如果data是另一种可迭代对象,比如listrange(都是序列),则两个循环都按预期运行:

>>> test([1, 2])
first loop
first loop
second loop
second loop
>>> test(range(2))
first loop
first loop
second loop
second loop

有关一般理论和术语解释,请参见What are iterator, iterable, and iteration?
要 * 检测 * 输入是迭代器还是“可重用”的可迭代对象,请参见Ensure that an argument can be iterated twice

hec6srdp

hec6srdp1#

迭代器只能使用一次。例如:

lst = [1, 2, 3]
it = iter(lst)

next(it)
# => 1
next(it)
# => 2
next(it)
# => 3
next(it)
# => StopIteration

当迭代器被提供给for循环时,最后一个StopIteration将导致它第一次退出,而试图在另一个for循环中使用相同的迭代器将立即再次导致StopIteration,因为迭代器已经被使用。
解决这个问题的一个简单方法是将所有元素保存到一个列表中,可以根据需要多次遍历该列表。

data = list(data)

但是,如果迭代器要迭代许多元素,那么使用tee()创建独立的迭代器是一个更好的主意:

import itertools
it1, it2 = itertools.tee(data, 2) # create as many as needed

现在可以依次迭代每一个:

for e in it1:
    print("first loop")

for e in it2:
    print("second loop")
rvpgvaaj

rvpgvaaj2#

迭代器(例如调用iter、生成器表达式或yield的生成器函数)是有状态的,只能使用一次。
Óscar López's answer中对此进行了解释,但是,该答案出于性能原因建议使用itertools.tee(data)而不是list(data),这是一种误导。在大多数情况下,当您希望迭代整个data,然后再次迭代整个data时,tee比简单地将整个迭代器消耗到一个列表中,然后在其上迭代两次要花费更多的时间和使用更多的内存。如果只使用每个迭代器的前几个元素,或者交替使用一个迭代器的几个元素和另一个迭代器的几个元素,则tee可能是首选。

l2osamch

l2osamch3#

一旦迭代器耗尽,它就不会再生成任何东西。

>>> it = iter([3, 1, 2])
>>> for x in it: print(x)
...
3
1
2
>>> for x in it: print(x)
...
>>>
mbjcgjjk

mbjcgjjk4#

如何在迭代器上循环两次?

这是不可能的!(稍后解释。)相反,请执行以下操作之一:

      • 将迭代器**收集到一个可以循环多次的东西中。
items = list(iterator)

for item in items:
    ...

缺点:这会消耗内存。

      • 创建一个新的迭代器。**创建一个新的迭代器通常只需要一微秒。
for item in create_iterator():
    ...

for item in create_iterator():
    ...

缺点:迭代本身可能是昂贵的(例如从磁盘或网络读取)。

      • 重置"迭代器"。**例如,对于文件迭代器:
with open(...) as f:
    for item in f:
        ...

    f.seek(0)

    for item in f:
        ...

缺点:大多数迭代器不能"重置"。

Iterator的哲学

世界分为两类:

      • Iterable:**一个用于保存数据的可循环数据结构。例如:一米一米一,一米二米一,一米三米一。
      • Iterator:**指向可迭代对象的某个元素的指针。

如果我们要定义一个序列迭代器,它可能看起来像这样:

class SequenceIterator:
    index: int
    items: Sequence  # Sequences can be randomly indexed via items[index].

    def __next__(self):
        """Increment index, and return the latest item."""

这里重要的是,* 通常 *,迭代器本身并不存储任何实际的数据。
迭代器通常是一个临时的数据流模型。这个数据源被迭代过程所消耗。这是一个很好的提示,说明了为什么一个任意的数据源不能循环超过一次。我们需要打开一个新的临时数据流(即创建一个新的迭代器)来做到这一点。

正在耗尽Iterator

当我们从一个迭代器中提取元素时,从迭代器的当前元素开始,一直持续到它完全耗尽,这就是for循环的作用:

iterable = "ABC"
iterator = iter(iterable)

for item in iterator:
    print(item)

让我们通过告诉for循环如何提取next项来支持SequenceIterator中的此功能:

class SequenceIterator:
    def __next__(self):
        item = self.items[self.index]
        self.index += 1
        return item

等等,如果index超过了items的最后一个元素,我们应该为此引发一个 * safe * 异常:

class SequenceIterator:
    def __next__(self):
        try:
            item = self.items[self.index]
        except IndexError:
            raise StopIteration  # Safely says, "no more items in iterator!"
        self.index += 1
        return item

现在,for循环知道何时停止从迭代器中提取项。
如果我们现在尝试再次遍历迭代器会发生什么?

iterable = "ABC"
iterator = iter(iterable)

# iterator.index == 0

for item in iterator:
    print(item)

# iterator.index == 3

for item in iterator:
    print(item)

# iterator.index == 3

由于第二个循环从当前的iterator.index(值3)开始,它没有其他要打印的内容,因此iterator.__next__引发StopIteration异常,导致循环立即结束。

lh80um4z

lh80um4z5#

尽可能不要在for循环中首先使用迭代器,这是不必要的,使用可迭代对象,iter(iterable)是python在执行for循环时调用的,一个可迭代对象可以循环多次。
如果你正在写代码,你发现你自己试图迭代一个迭代器,你的代码中一定有一些不自然的地方,试着先修复它。

相关问题