Можно ли сбросить итераторы в Python? - PullRequest
105 голосов
/ 16 июля 2010

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

Ответы [ 13 ]

70 голосов
/ 16 июля 2010

Я вижу много ответов, предлагающих itertools.tee , но в документах для него игнорируется одно важное предупреждение:

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

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

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

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

29 голосов
/ 16 июля 2010

Если у вас есть CSV-файл с именем 'blah.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(), вы получите

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

еще раз.

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

21 голосов
/ 16 июля 2010

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

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

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

10 голосов
/ 30 декабря 2012

Да , если вы используете 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
10 голосов
/ 16 июля 2010

Существует ошибка в использовании .seek (0), о чем говорили Алекс Мартелли и Уилдак выше, а именно, что следующий вызов .next () даст вам словарь строки заголовка в виде {key1: key1,ключ2: ключ2, ...}.Обходной путь заключается в том, чтобы следовать file.seek (0) с вызовом reader.next (), чтобы избавиться от строки заголовка.

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

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)
4 голосов
/ 19 февраля 2015

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

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 копий) или списка (1 копия)

2 голосов
/ 16 июля 2010

Хотя сброс итератора отсутствует, модуль «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]
1 голос
/ 31 января 2018

Задача

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

Решение

Откройте файл и сохраните строки в переменной в памяти.

# initialize list of rows
rows = []

# open the file and temporarily name it as 'my_file'
with open('myfile.csv', 'rb') as my_file:

    # set up the reader using the opened file
    myfilereader = csv.DictReader(my_file)

    # loop through each row of the reader
    for row in myfilereader:
        # add the row to the list of rows
        rows.append(row)

Теперь вы можете циклически проходить по строкам в любом месте области видимости, не обращаясь к итератору.

1 голос
/ 05 декабря 2017

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

Демо

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 обернут в seekable объект (1) и расширенный (2).Метод seek() используется для сброса / перемотки итератора в 0-ю позицию (3).

Примечание: потребление памяти увеличивается с итерацией, поэтому будьте осторожны, применяя этот инструмент для больших файлов, как указано в документах .

0 голосов
/ 12 января 2019

Возможный вариант - использовать itertools.cycle () , что позволит бесконечно выполнять итерации без каких-либо уловок, например .seek (0)

iterDic = itertools.cycle(csv.DictReader(open('file.csv')))
...