Чи можна скинути ітератори в Python?


Відповіді:


84

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

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

В основному, teeвін розроблений для тих ситуацій, коли два (або більше) клонів одного ітератора, хоча "виходять із синхронізації" один з одним, не роблять цього багато, швидше, вони говорять в одній "околиці" ( кілька предметів позаду або попереду один одного). Не підходить для проблеми ОП "переробити з самого початку".

L = list(DictReader(...))з іншого боку, цілком підходить до тих пір, поки список диктів може комфортно вміститися в пам'яті. Новий «ітератор з самого початку» (дуже легкий і низький накладні витрати) можна виготовити в будь-який час разом із ним 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/…
Simon

11

Існує помилка у використанні, .seek(0)як пропонують Алекс Мартеллі та Вілдук вище, а саме наступний дзвінок .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)

Це здається, щоб уникнути кешування, яке потрібно виконати трійці (n копій) або списку (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()оператори та використовую перший для мого початкового пробігу, перш ніж переходити на другий для остаточного виконання.

Отже, у випадку з читачем диктовок, якщо читач визначений за допомогою:

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

Я можу створити пару ітераторів із цієї "специфікації" - використовуючи:

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

Потім я можу запустити свій код 1-го проходу d1, впевнений, що другий ітератор d2визначений з тієї ж кореневої специфікації.

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



0

Повернути новостворений ітератор під час останньої ітерації під час виклику '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.