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


130

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



list()Кстати , я обнаружил, что функция будет перебирать свой аргумент (итерация). Таким образом, list()дважды вызывая одну и ту же итерацию (например, результат zip()), вы получите пустой список при втором вызове!
theaws.blog

Ответы:


84

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

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

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

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

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


6
Использование list()для кеширования многопроходного режима через csvreader в файле размером 5 МБ приводит к тому, что мое время выполнения меняется с ~ 12 секунд до ~ 0,5 секунды.
Джон Ми

33

Если у вас есть файл 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. Это не сработает, если вы первый читатель на полпути к чтению файла, так как у вашего нового читателя будут неожиданные ключи и значения из любого места в файле.


Это было то, что мне сказала моя теория, и приятно видеть, что то, что я думал, должно произойти, действительно происходит.
Уэйн Вернер

@Wilduck: поведение, которое вы описываете с другим экземпляром DictReader, не произойдет, если вы создадите новый дескриптор файла и передадите его второму DictReader, верно?

Если у вас есть два обработчика файлов, они будут вести себя независимо, да.
Wilduck

24

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

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

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


1
Хотя ваш анализ метода .next (), вероятно, верен, есть довольно простой способ получить то, что запрашивает оператор.
Wilduck

2
@Wilduck: Я вижу ваш ответ. Я только что ответил на вопрос итератора и понятия не имею о csvмодуле. Надеюсь, оба ответа будут полезны для исходного плаката.
u0b34a0f6ae

Строго говоря, протокол итератора также требует __iter__. То есть итераторы также должны быть итерируемыми.
Стив Джессоп

11

Да , если вы используете 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

Может nditerциклично по массиву вроде itertools.cycle?
LWZ

1
@LWZ: Я не думаю , что это так, но вы можете и на исключение делать . try:next()StopIterationreset()
Приостановлено до дальнейшего уведомления.


Это то, что я искал!
sriram

1
Обратите внимание, что предел «операндов» здесь равен 32: stackoverflow.com/questions/51856685/…
Саймон

11

Есть ошибка в использовании, которую .seek(0)пропагандировали Алекс Мартелли и Уилдак выше, а именно то, что следующий вызов to .next()даст вам словарь строки заголовка в форме {key1:key1, key2:key2, ...}. Чтобы обойти эту проблему, нужно выполнить 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)

5

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

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 копия).


3

Для небольших файлов вы можете рассмотреть возможность использования 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'}

Здесь a DictReaderзаключен в seekableобъект (1) и расширен (2). seek()Метод используется для сброса / назад итератора в 0 - ом положении (3).

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


2

Хотя сброса итератора отсутствует, в модуле 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

Для 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()

1

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


1

проблема

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

Решение

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

# 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

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

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

1

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

Вместо этого я создаю пару итераторов с помощью iter()операторов и использую первый для моего начального прогона, прежде чем переключиться на второй для окончательного прогона.

Итак, в случае dict-reader, если читатель определен с помощью:

d = csv.DictReader(f, delimiter=",")

Я могу создать пару итераторов из этой «спецификации» - используя:

d1, d2 = iter(d), iter(d)

Затем я могу запустить свой код 1-го прохода d1, зная, что второй итератор d2определен из той же корневой спецификации.

Я не проверял это полностью, но, похоже, он работает с фиктивными данными.


1

Вернуть вновь созданный итератор на последней итерации во время вызова iter ()

class ResetIter: 
  def __init__(self, num):
    self.num = num
    self.i = -1

  def __iter__(self):
    if self.i == self.num-1: # here, return the new object
      return self.__class__(self.num) 
    return self

  def __next__(self):
    if self.i == self.num-1:
      raise StopIteration

    if self.i <= self.num-1:
      self.i += 1
      return self.i


reset_iter = ResetRange(10)
for i in reset_iter:
  print(i, end=' ')
print()

for i in reset_iter:
  print(i, end=' ')
print()

for i in reset_iter:
  print(i, end=' ')

Вывод:

0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 5 6 7 8 9 

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.