为啥我不能对相同的数据进行两次迭代?

Posted

技术标签:

【中文标题】为啥我不能对相同的数据进行两次迭代?【英文标题】:Why can't I iterate twice over the same data?为什么我不能对相同的数据进行两次迭代? 【发布时间】:2014-08-16 03:42:47 【问题描述】:

为什么我不能在同一个iterator 上迭代两次?

# data is an iterator.

for row in data:
    print("doing this one time")

for row in data:
    print("doing this two times")

这会打印几次"doing this one time",因为data 是非空的。但是,它打印"doing this two times"。为什么第一次迭代 data 可以工作,但第二次不行?

【问题讨论】:

可迭代与迭代器。 我并不是说这是重复的,但您可能还想参考***.com/questions/9884132/… 以获得更多上下文/解释 相关:Resetting an iterator object 此问题中提供的代码并不是重现问题的最短时间。可以通过提供更好的代码示例来改进这个问题。 @Trilarion 是的,我认为 def _view(self,dbName): db = self.dictDatabases[dbName] data = db[3] 可以安全删除,因为没有其他答案讨论该部分代码。 【参考方案1】:

这是因为data 是一个迭代器,一个迭代器只能使用一次。例如:

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

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

如果我们使用for 循环遍历一些数据,最后一个StopIteration 将导致它第一次退出。如果我们尝试再次对其进行迭代,我们将不断收到StopIteration 异常,因为迭代器已被消耗。


现在是第二个问题:如果我们确实需要多次遍历迭代器怎么办?一个简单的解决方案是将所有元素保存到一个列表中,可以根据需要多次遍历该列表。例如,如果 data 是一个迭代器:

data = list(data)

只要列表中的元素很少就可以。但是,如果有很多元素,最好使用tee() 创建独立的迭代器:

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

现在我们可以依次循环遍历每一个:

for e in it1:
    print("doing this one time")

for e in it2:
    print("doing this two times")

【讨论】:

@ÓscarLópez 来自tee 文档的注释:“此迭代工具可能需要大量辅助存储(取决于需要存储多少临时数据)。一般来说,如果一个迭代器使用大部分或全部在另一个迭代器开始之前的数据,使用 list() 而不是 tee() 更快。"因此,如果您像示例中一样使用it1it2,您可能无法从tee 中获得任何真正的好处(同时可能需要一些额外的开销)。 我支持@svk - 在这种情况下tee 将创建迭代器值的完整副本,其效率略低于单个list 调用。当可迭代元素有很多时,不应该使用tee - 这不相关,但是当存在使用局部性时 - 在这种情况下,tee 的缓存可能小于整个列表。例如,如果两个迭代器并驾齐驱,例如在zip(a, islice(b, 1)) 调用中。 @user2357112supportsMonica 您对此答案的编辑正在meta 上讨论。【参考方案2】:

迭代器(例如来自调用iter、来自生成器表达式或来自yield 的生成器函数)是有状态的,并且只能使用一次,如Óscar López's answer 中所述。但是,出于性能原因,该答案建议使用itertools.tee(data) 而不是list(data) 具有误导性。

在大多数情况下,如果您想遍历整个 data 然后再次遍历整个它,tee 比简单地将整个迭代器消耗到一个列表中然后迭代它两次。如果您只使用每个迭代器的前几个元素,或者如果您将交替使用来自一个迭代器的几个元素,然后使用另一个迭代器的几个元素,tee 可能是首选。

【讨论】:

应该可能只链接到哈希(只是#25336738),因为需要重新加载整个页面是很糟糕的 @somebody 我不认为这是可能的 - 至少,我试过了,Stack Overflow 的编辑器并没有把它变成一个链接。如果您知道该怎么做,那么我建议您进行编辑。 :( 不幸的是,这似乎不可能(请参阅this feature request) - 似乎下一个最好的事情是假设用户点击了问题(而不是其中一个答案):@ 987654323@【参考方案3】:

一旦迭代器用完,它就不会再产生了。

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

【讨论】:

有道理,但我该如何解决呢? @JSchwartz,将迭代器转换为序列对象(listtuple)。然后迭代序列对象。 (仅当 csv 的大小不是很大时) @JSchwartz,或者,如果您可以访问底层文件对象并且是可搜索的。您可以在第二个循环之前更改文件位置:csv_file_object.seek(0)【参考方案4】:

如何循环遍历一个迭代器两次?

这是不可能的! (稍后解释。)改为:

    将迭代器收集成一个可以循环多次的东西。

    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: 用于保存数据的 for-loopable 数据结构。示例:listtuplestr迭代器:指向可迭代对象的某个元素的指针。

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

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 的最后一个元素怎么办?我们应该为此提出一个安全异常:

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 异常,导致循环立即结束。

【讨论】:

以上是关于为啥我不能对相同的数据进行两次迭代?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我似乎不能在 python pymysql 中迭代地填充一个 sql 表?

为啥 Swift 中的过滤器会迭代集合两次?

为啥不能两次获取相同的数据? NSFetchedResultsController 为空

如何对迭代中类型发生变化但函数形式相同的函数进行“迭代”

Python list 可以在迭代期间发生变异,但不能在 deque 中发生变异。为啥?

为啥我不能在 foreach 循环中设置迭代变量的属性?