Можно ли сбросить итераторы в Python?

могу ли я сбросить итератор / генератор в Python? Я использую DictReader и хотел бы сбросить его (из модуля csv) в начало файла.

12 ответов


Я вижу много ответов, предлагая itertools.ти, но это игнорирует одно важное предупреждение в документации к ней:

этот itertool может потребовать значительного вспомогательной памяти (в зависимости от много временных данных должно быть хранящийся.) В общем случае, если один итератор использует большинство или все данные перед запускается другой итератор, он быстрее использовать list() вместо tee().

по сути, tee предназначен для тех, ситуация, когда два (или более) клона одного итератора, "выходя из синхронизации" друг с другом, не делают этого много -- скорее, они говорят в одной и той же "близости" (несколько предметов позади или впереди друг друга). Не подходит для проблемы ОП "повторить с самого начала".

L = list(DictReader(...)) С другой стороны, идеально подходит, пока список диктов может удобно поместиться в памяти. Новый "итератор с самого начала" (очень легкий и с низкими накладными расходами) может быть сделан на в любое время с iter(L), и использованный частично или в целом без влияния новых или существующих одних; другие картины доступа также легко доступны.

как справедливо заметили несколько ответов, в конкретном случае csv вы можете .seek(0) базовый файловый объект (довольно частный случай). Я не уверен, что это документировано и гарантировано, хотя в настоящее время это работает; вероятно, стоит рассмотреть только для действительно огромных csv-файлов, в которых list я рассказывать об Как общий подход будет иметь слишком большой объем памяти.


если у вас есть файл csv с именем 'бла.csv ' это выглядит как

a,b,c,d
1,2,3,4
2,3,4,5
3,4,5,6

вы знаете, что вы можете открыть файл для чтения и создания DictReader с

blah = open('blah.csv', 'r')
reader= csv.DictReader(blah)

тогда, вы сможете получить следующую строку с reader.next(), которая должна вывести

{'a':1,'b':2,'c':3,'d':4}

снова произведет

{'a':2,'b':3,'c':4,'d':5}

однако, на данный момент, если вы используете blah.seek(0), в следующий раз, когда вы называете reader.next() вы get

{'a':1,'b':2,'c':3,'d':4}

снова.

это, кажется, функциональность, которую вы ищете. Я уверен, что есть некоторые трюки, связанные с этим подходом, о которых я не знаю. @Brian предложил просто создать еще один диктофон. Это не будет работать, если вы первый читатель на полпути через чтение файла, как ваш новый читатель будет иметь неожиданные ключи и значения из любого места в файле.


нет. Протокол итератора Python очень прост и предоставляет только один единственный метод (.next() или __next__()), и нет метода сброса итератора в целом.

общий шаблон состоит в том, чтобы вместо этого создать новый итератор, используя ту же процедуру снова.

Если вы хотите "сохранить" итератор, чтобы вы могли вернуться к его началу, вы также можете развить итератор с помощью itertools.tee


в использовании есть ошибка .искать(0), как отстаивали Алекс Мартелли и Вилдак выше, а именно, что следующий вызов .next () даст вам словарь строки заголовка в виде {key1:key1, key2:key2, ...}. Работа вокруг, чтобы следовать файл.поиск(0) с вызовом reader.следующий() чтобы избавиться от строки заголовка.

Так что ваш код будет выглядеть примерно так:

f_in = open('myfile.csv','r')
reader = csv.DictReader(f_in)

for record in reader:
    if some_condition:
        # reset reader to first row of data on 2nd line of file
        f_in.seek(0)
        reader.next()
        continue
    do_something(record)

Да, если вы используете numpy.nditer для создания итератора.

>>> lst = [1,2,3,4,5]
>>> itr = numpy.nditer([lst])
>>> itr.next()
1
>>> itr.next()
2
>>> itr.finished
False
>>> itr.reset()
>>> itr.next()
1

пока нет сброса итератора, модуль "itertools" из python 2.6 (и более поздних версий) имеет некоторые утилиты, которые могут помочь там. Один из них - "тройник", который может сделать несколько копий итератора и кэшировать результаты одного из них, чтобы эти результаты использовались на копиях. Я разделю ваши цели:

>>> def printiter(n):
...   for i in xrange(n):
...     print "iterating value %d" % i
...     yield i

>>> from itertools import tee
>>> a, b = tee(printiter(5), 2)
>>> list(a)
iterating value 0
iterating value 1
iterating value 2
iterating value 3
iterating value 4
[0, 1, 2, 3, 4]
>>> list(b)
[0, 1, 2, 3, 4]

это, возможно, ортогонально исходному вопросу, но можно обернуть итератор в функцию, которая возвращает итератор.

def get_iter():
    return iterator

сбросить итератор просто снова вызовите функцию. Это, конечно, тривиально, если функция, когда указанная функция не принимает аргументов.

в случае, если функция требует аргументов, использовать functools.partial для создания закрытия, которое может быть передано вместо исходного итератор.

def get_iter(arg1, arg2):
   return iterator
from functools import partial
iter_clos = partial(get_iter, a1, a2)

это, похоже, позволяет избежать кэширования, которое tee (N копий) или list (1 копия) должны были бы сделать


для небольших файлов, вы можете использовать more_itertools.seekable - сторонний инструмент, который предлагает сброс iterables.

демо

import csv

import more_itertools as mit


filename = "data/iris.csv"
with open(filename, "r") as f:
    reader = csv.DictReader(f)
    iterable = mit.seekable(reader)                    # 1
    print(next(iterable))                              # 2
    print(next(iterable))
    print(next(iterable))

    print("\nReset iterable\n--------------")
    iterable.seek(0)                                   # 3
    print(next(iterable))
    print(next(iterable))
    print(next(iterable))

выход

{'Sepal width': '3.5', 'Petal width': '0.2', 'Petal length': '1.4', 'Sepal length': '5.1', 'Species': 'Iris-setosa'}
{'Sepal width': '3', 'Petal width': '0.2', 'Petal length': '1.4', 'Sepal length': '4.9', 'Species': 'Iris-setosa'}
{'Sepal width': '3.2', 'Petal width': '0.2', 'Petal length': '1.3', 'Sepal length': '4.7', 'Species': 'Iris-setosa'}

Reset iterable
--------------
{'Sepal width': '3.5', 'Petal width': '0.2', 'Petal length': '1.4', 'Sepal length': '5.1', 'Species': 'Iris-setosa'}
{'Sepal width': '3', 'Petal width': '0.2', 'Petal length': '1.4', 'Sepal length': '4.9', 'Species': 'Iris-setosa'}
{'Sepal width': '3.2', 'Petal width': '0.2', 'Petal length': '1.3', 'Sepal length': '4.7', 'Species': 'Iris-setosa'}
на DictReader обернут в

только если базовый тип обеспечивает механизм для этого (например,fp.seek(0)).


Для DictReader:

f = open(filename, "rb")
d = csv.DictReader(f, delimiter=",")

f.seek(0)
d.__init__(f, delimiter=",")

Для DictWriter:

f = open(filename, "rb+")
d = csv.DictWriter(f, fieldnames=fields, delimiter=",")

f.seek(0)
f.truncate(0)
d.__init__(f, fieldnames=fields, delimiter=",")
d.writeheader()
f.flush()

list(generator()) возвращает все оставшиеся значения для генератора и эффективно сбрасывает его, если он не зациклен.