Czy można zresetować iteratory w Pythonie?


130

Czy mogę zresetować iterator / generator w Pythonie? Używam DictReader i chciałbym zresetować go do początku pliku.


1
Możliwy duplikat obiektu Reseting generatora w Pythonie
sschuberth

Na marginesie stwierdziłem, że list()funkcja będzie iterować przez swój argument (iterowalny). W ten sposób wywołując list()dwukrotnie tę samą iterowalną opcję (np. Wynik zip()), przy drugim wywołaniu otrzymasz pustą listę!
theaws.blog

Odpowiedzi:


84

Widzę wiele odpowiedzi sugerujących itertools.tee , ale to ignoruje jedno kluczowe ostrzeżenie w dokumentacji:

To narzędzie itertool może wymagać znacznej ilości pamięci dyskowej (w zależności od tego, ile danych tymczasowych należy przechowywać). Ogólnie rzecz biorąc, jeśli jeden iterator używa większości lub wszystkich danych przed uruchomieniem innego iteratora, jest szybszy w użyciu list()zamiast tee().

Zasadniczo teejest przeznaczony do sytuacji, w których dwa (lub więcej) klony jednego iteratora, „tracąc synchronizację” ze sobą, nie robią tego zbytnio - raczej mówią w tym samym „sąsiedztwie” (a kilka elementów za lub przed sobą). Nie nadaje się do problemu „ponów od początku” w PO.

L = list(DictReader(...))z drugiej strony jest idealny, o ile lista dyktandów mieści się wygodnie w pamięci. Nowy „iterator od samego początku” (bardzo lekki i niewielki) może być utworzony w dowolnym momencie iter(L)i używany w części lub w całości bez wpływu na nowe lub istniejące; łatwo dostępne są również inne wzorce dostępu.

Jak słusznie zauważono w kilku odpowiedziach, w konkretnym przypadku csvmożesz również podać .seek(0)podstawowy obiekt pliku (raczej szczególny przypadek). Nie jestem pewien, czy jest to udokumentowane i gwarantowane, chociaż obecnie działa; prawdopodobnie warto byłoby rozważyć to tylko dla naprawdę dużych plików csv, w których listpolecam, ponieważ podejście ogólne miałoby zbyt duży ślad pamięci.


6
Używając list()do buforowania wielu przejść przez csvreader w pliku o wielkości 5 MB, mój czas działania skraca się z ~ 12 sekund do ~ 0,5 sekundy.
John Mee,

33

Jeśli masz plik csv o nazwie „blah.csv”, to wygląda tak

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

wiesz, że możesz otworzyć plik do czytania i utworzyć DictReader za pomocą

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

Wtedy będziesz mógł pobrać następną linię reader.next(), która powinna zostać wyświetlona

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

ponowne użycie go przyniesie

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

Jednak w tym momencie, jeśli użyjesz blah.seek(0), następnym razem, gdy zadzwonisz reader.next(), otrzymasz

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

jeszcze raz.

Wydaje się, że jest to funkcja, której szukasz. Jestem pewien, że są pewne sztuczki związane z tym podejściem, których jednak nie jestem świadomy. @Brian zasugerował po prostu utworzenie kolejnego DictReadera. To nie zadziała, jeśli pierwszy czytelnik jest w połowie czytania pliku, ponieważ nowy czytnik będzie miał nieoczekiwane klucze i wartości z dowolnego miejsca w pliku.


Tak powiedziała mi moja teoria, miło widzieć, że to, co myślałem, powinno się wydarzyć.
Wayne Werner,

@Wilduck: zachowanie, które opisujesz za pomocą innej instancji DictReader, nie nastąpi, jeśli utworzysz nowy uchwyt pliku i przekażesz go do drugiego DictReader, prawda?

Jeśli masz dwa programy obsługi plików, będą one działać niezależnie, tak.
Wilduck

24

Nie. Protokół iteratora w Pythonie jest bardzo prosty i zapewnia tylko jedną metodę ( .next()lub __next__()) i nie ma żadnej metody resetowania iteratora w ogóle.

Typowym wzorcem jest zamiast tego utworzenie nowego iteratora przy użyciu tej samej procedury ponownie.

Jeśli chcesz "zapisać" iterator, aby móc wrócić do jego początku, możesz również rozwidlić iterator za pomocą itertools.tee


1
Chociaż analiza metody .next () jest prawdopodobnie poprawna, istnieje dość prosty sposób na uzyskanie tego, o co prosi operacja.
Wilduck,

2
@Wilduck: Widzę, że twoja odpowiedź. Właśnie odpowiedziałem na pytanie iteratora i nie mam pojęcia o csvmodule. Miejmy nadzieję, że obie odpowiedzi będą przydatne w oryginalnym plakacie.
u0b34a0f6ae

Ściśle rzecz biorąc, protokół iteratora również wymaga __iter__. Oznacza to, że iteratory również muszą być iterowalne.
Steve Jessop

11

Tak , jeśli używasz numpy.nditerdo budowania swojego iteratora.

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

Czy można nditerprzechodzić przez tablicę itertools.cycle?
LWZ

1
@LWZ: Nie sądzę więc, ale można i na wyjątku zrobić . try:next()StopIterationreset()
Wstrzymano do odwołania.

... a następnienext()
Wstrzymano do odwołania.

To jest to, czego szukałem!
sriram,

1
Zauważ, że limit "operandów" tutaj wynosi 32: stackoverflow.com/questions/51856685/…
Simon

11

Jest błąd w używaniu, .seek(0)jak zalecali Alex Martelli i Wilduck powyżej, a mianowicie, że następne wywołanie .next()da ci słownik twojego wiersza nagłówka w postaci {key1:key1, key2:key2, ...}. Obejście polega na file.seek(0)wywołaniu polecenia, reader.next()aby pozbyć się wiersza nagłówka.

Twój kod wyglądałby więc mniej więcej tak:

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

Jest to prawdopodobnie prostopadłe do pierwotnego pytania, ale można by opakować iterator w funkcję, która zwraca iterator.

def get_iter():
    return iterator

Aby zresetować iterator, po prostu ponownie wywołaj funkcję. Jest to oczywiście trywialne, jeśli funkcja, gdy wspomniana funkcja nie przyjmuje argumentów.

W przypadku, gdy funkcja wymaga pewnych argumentów, użyj functools.partial, aby utworzyć zamknięcie, które można przekazać zamiast oryginalnego iteratora.

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

Wydaje się, że pozwala to uniknąć buforowania, które musiałyby zrobić tee (n kopii) lub list (1 kopia)


3

W przypadku małych plików możesz rozważyć użycie more_itertools.seekable- narzędzia innej firmy, które oferuje resetowanie iterowalnych.

Próbny

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))

Wynik

{'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'}

Tutaj a DictReaderjest zawinięty w seekableobiekt (1) i zaawansowany (2). seek()Sposób służy do zerowania / tyłu iteracyjnej do położenia 0th (3).

Uwaga: zużycie pamięci rośnie wraz z iteracją, więc zachowaj ostrożność, stosując to narzędzie do dużych plików, jak wskazano w dokumentacji .


2

Chociaż nie ma resetowania iteratora, moduł „itertools” z Pythona 2.6 (i nowszych) ma kilka narzędzi, które mogą w tym pomóc. Jednym z nich jest „tee”, który może tworzyć wiele kopii iteratora i buforować wyniki kolejnego, tak aby były one używane na kopiach. Zepsuję twoje cele:

>>> 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

W przypadku DictReader:

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

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

W przypadku 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()) zwraca wszystkie pozostałe wartości dla generatora i skutecznie resetuje go, jeśli nie jest zapętlony.


1

Problem

Miałem wcześniej ten sam problem. Po przeanalizowaniu mojego kodu zdałem sobie sprawę, że próba zresetowania iteratora wewnątrz pętli nieznacznie zwiększa złożoność czasową, a także sprawia, że ​​kod jest nieco brzydki.

Rozwiązanie

Otwórz plik i zapisz wiersze w zmiennej w pamięci.

# 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)

Teraz możesz zapętlać wiersze w dowolnym miejscu zakresu bez korzystania z iteratora.


1

Jedną z możliwych opcji jest użycie itertools.cycle(), które pozwoli ci iterować w nieskończoność bez żadnych sztuczek, takich jak .seek(0).

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

1

Dochodzę do tego samego problemu - chociaż podoba mi się to tee()rozwiązanie, nie wiem, jak duże będą moje pliki, a ostrzeżenia dotyczące pamięci o zużyciu jednego przed drugim zniechęcają mnie do przyjęcia tej metody.

Zamiast tego tworzę parę iteratorów za pomocą iter()instrukcji i używam pierwszego do mojego początkowego uruchomienia, przed przełączeniem się na drugi dla końcowego przebiegu.

Tak więc w przypadku czytnika dyktafonu, jeśli czytelnik jest zdefiniowany za pomocą:

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

Mogę utworzyć parę iteratorów na podstawie tej „specyfikacji” - używając:

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

Mogę wtedy uruchomić kod pierwszego przejścia d1, mając pewność, że drugi iterator d2został zdefiniowany na podstawie tej samej specyfikacji głównej.

Nie testowałem tego do końca, ale wydaje się, że działa z fikcyjnymi danymi.


1

Zwraca nowo utworzony iterator w ostatniej iteracji podczas wywołania „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=' ')

Wynik:

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 

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.