Können Iteratoren in Python zurückgesetzt werden?


Antworten:


83

Ich sehe viele Antworten, die auf itertools.tee hindeuten , aber das ignoriert eine entscheidende Warnung in den Dokumenten dafür:

Dieses Tool erfordert möglicherweise einen erheblichen zusätzlichen Speicher (abhängig davon, wie viele temporäre Daten gespeichert werden müssen). Im Allgemeinen, wenn man verwendet Iterator die meisten oder alle Daten , bevor eine andere Iterator beginnt, ist es schneller zu verwenden list()statt tee().

Grundsätzlich teeist es für Situationen gedacht, in denen zwei (oder mehr) Klone eines Iterators, während sie "nicht mehr synchron" sind, dies nicht viel tun - vielmehr sagen sie in derselben "Umgebung" (a wenige Gegenstände hinter oder voreinander). Nicht geeignet für das OP-Problem "Von Anfang an wiederholen".

L = list(DictReader(...))Auf der anderen Seite ist es perfekt geeignet, solange die Liste der Diktate bequem in Erinnerung bleibt. Ein neuer "Iterator von Anfang an" (sehr leicht und mit geringem Overhead) kann jederzeit mit erstellt iter(L)oder ganz oder teilweise verwendet werden, ohne dass neue oder vorhandene davon betroffen sind. andere Zugriffsmuster sind ebenfalls leicht verfügbar.

Wie mehrere Antworten zu Recht bemerkten, können Sie im konkreten Fall csvauch .seek(0)das zugrunde liegende Dateiobjekt (ein eher spezieller Fall). Ich bin nicht sicher, ob dies dokumentiert und garantiert ist, obwohl es derzeit funktioniert. Es lohnt sich wahrscheinlich, nur für wirklich große CSV-Dateien in Betracht zu ziehen, bei denen listich empfehle, da der allgemeine Ansatz einen zu großen Speicherbedarf hätte.


6
Wenn list()ich Multipassage über einen CSV-Reader in einer 5-MB-Datei zwischenspeichere, geht meine Laufzeit von ~ 12 Sekunden auf ~ 0,5 Sekunden.
John Mee

33

Wenn Sie eine CSV-Datei mit dem Namen 'blah.csv' haben, sieht das so aus

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

Sie wissen, dass Sie die Datei zum Lesen öffnen und mit einen DictReader erstellen können

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

Dann können Sie die nächste Zeile mit erhalten reader.next(), die ausgegeben werden soll

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

Wenn Sie es erneut verwenden, wird es erzeugt

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

Wenn Sie jedoch an dieser Stelle verwenden blah.seek(0), das nächste Mal , wenn Sie rufen reader.next()Sie erhalten

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

nochmal.

Dies scheint die Funktionalität zu sein, nach der Sie suchen. Ich bin mir sicher, dass mit diesem Ansatz einige Tricks verbunden sind, die mir jedoch nicht bekannt sind. @Brian schlug vor, einfach einen weiteren DictReader zu erstellen. Dies funktioniert nicht, wenn Ihr erster Leser die Datei zur Hälfte gelesen hat, da Ihr neuer Leser unerwartete Schlüssel und Werte von jedem Ort in der Datei hat.


Das sagte mir meine Theorie, schön zu sehen, dass das, was ich dachte, passieren sollte.
Wayne Werner

@ Wilduck: Das Verhalten, das Sie mit einer anderen Instanz von DictReader beschreiben, tritt nicht auf, wenn Sie ein neues Dateihandle erstellen und dieses an den zweiten DictReader übergeben, oder?

Wenn Sie zwei Dateihandler haben, verhalten sich diese unabhängig voneinander, ja.
Wilduck

24

Nein. Das Iteratorprotokoll von Python ist sehr einfach und bietet nur eine einzige Methode ( .next()oder __next__()) und keine Methode zum Zurücksetzen eines Iterators im Allgemeinen.

Das übliche Muster besteht darin, stattdessen erneut einen neuen Iterator mit demselben Verfahren zu erstellen.

Wenn Sie einen Iterator "speichern" möchten, damit Sie zu seinem Anfang zurückkehren können, können Sie den Iterator auch mit verwenden itertools.tee


1
Während Ihre Analyse der .next () -Methode wahrscheinlich korrekt ist, gibt es einen ziemlich einfachen Weg, um herauszufinden, wonach die Operation fragt.
Wilduck

2
@ Wilduck: Ich sehe, dass deine Antwort. Ich habe gerade die Iteratorfrage beantwortet und habe keine Ahnung von dem csvModul. Hoffentlich sind beide Antworten für das Originalplakat nützlich.
u0b34a0f6ae

Streng genommen erfordert das Iteratorprotokoll auch __iter__. Das heißt, Iteratoren müssen auch iterabel sein.
Steve Jessop

11

Ja , wenn Sie numpy.nditerIhren Iterator erstellen.

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

Kann man nditerwie durch das Array radeln itertools.cycle?
LWZ

1
@LWZ: Ich denke nicht, aber du kannst try:das next()und StopIterationausnahmsweise ein machen reset().
Bis auf weiteres angehalten.

... gefolgt von anext()
Bis auf weiteres angehalten.

Das habe ich gesucht!
Sriram

1
Beachten Sie, dass die Grenze der "Operanden" hier 32 ist: stackoverflow.com/questions/51856685/…
Simon

11

Es gibt einen Fehler bei der Verwendung, .seek(0)wie von Alex Martelli und Wilduck oben befürwortet, nämlich, dass Sie beim nächsten Aufruf .next()ein Wörterbuch Ihrer Kopfzeile in Form von erhalten {key1:key1, key2:key2, ...}. Die Problemumgehung besteht darin, file.seek(0)mit einem Aufruf zu folgen reader.next(), um die Kopfzeile zu entfernen.

Ihr Code würde also ungefähr so ​​aussehen:

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

Dies ist vielleicht orthogonal zur ursprünglichen Frage, aber man könnte den Iterator in eine Funktion einschließen, die den Iterator zurückgibt.

def get_iter():
    return iterator

Um den Iterator zurückzusetzen, rufen Sie einfach die Funktion erneut auf. Dies ist natürlich trivial, wenn die Funktion, wenn diese Funktion keine Argumente akzeptiert.

Für den Fall, dass die Funktion einige Argumente erfordert, verwenden Sie functools.partial, um einen Abschluss zu erstellen, der anstelle des ursprünglichen Iterators übergeben werden kann.

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

Dies scheint das Caching zu vermeiden, das Tee (n Kopien) oder Liste (1 Kopie) durchführen müsste


3

Für kleine Dateien können Sie die Verwendung in Betracht ziehen more_itertools.seekable eines Tools von Drittanbietern in das das Zurücksetzen von Iterables ermöglicht.

Demo

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

Ausgabe

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

Hier DictReaderwird a in ein seekableObjekt (1) eingewickelt und weiterentwickelt (2). Die seek()Methode wird verwendet, um den Iterator auf die 0. Position (3) zurückzusetzen / zurückzuspulen.

Hinweis: Der Speicherverbrauch steigt mit der Iteration. Seien Sie also vorsichtig, wenn Sie dieses Tool auf große Dateien anwenden, wie in den Dokumenten angegeben .


2

Während es kein Zurücksetzen des Iterators gibt, verfügt das "itertools" -Modul von Python 2.6 (und höher) über einige Dienstprogramme, die dort helfen können. Eines davon ist das "T-Stück", mit dem mehrere Kopien eines Iterators erstellt und die Ergebnisse des vorauslaufenden Iterers zwischengespeichert werden können, sodass diese Ergebnisse für die Kopien verwendet werden. Ich werde Ihre Zwecke sieben:

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

Für DictReader:

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

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

Für 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()) Gibt alle verbleibenden Werte für einen Generator zurück und setzt ihn effektiv zurück, wenn er nicht geloopt ist.


1

Problem

Ich hatte das gleiche Problem schon einmal. Nachdem ich meinen Code analysiert hatte, stellte ich fest, dass der Versuch, den Iterator innerhalb von Schleifen zurückzusetzen, die Zeitkomplexität geringfügig erhöht und den Code auch etwas hässlich macht.

Lösung

Öffnen Sie die Datei und speichern Sie die Zeilen in einer Variablen im Speicher.

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

Jetzt können Sie Zeilen an einer beliebigen Stelle in Ihrem Bereich durchlaufen, ohne sich mit einem Iterator befassen zu müssen.


1

Eine mögliche Option ist die Verwendung itertools.cycle(), mit der Sie unbegrenzt iterieren können, ohne einen Trick wie .seek(0).

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

1

Ich komme zu dem gleichen Thema - während ich das mag tee() Lösung gefällt, weiß ich nicht, wie groß meine Dateien sein werden, und die Speicherwarnungen vor dem erstmaligen Konsumieren einer Datei vor der anderen hindern mich daran, diese Methode anzuwenden.

Stattdessen erstelle ich ein Paar von Iteratoren mithilfe von iter()Anweisungen und verwende den ersten für meinen ersten Durchlauf, bevor ich für den letzten Durchlauf zum zweiten wechsle.

Also, im Fall eines Diktierlesers, wenn der Leser definiert ist mit:

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

Ich kann aus dieser "Spezifikation" ein Paar Iteratoren erstellen - mit:

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

Ich kann dann meinen 1st-Pass-Code ausführen d1, sicher in dem Wissen, dass der zweite Iteratord2 aus derselben definiert wurde.

Ich habe dies nicht ausführlich getestet, aber es scheint mit Dummy-Daten zu funktionieren.



0

Gibt einen neu erstellten Iterator bei der letzten Iteration während des Aufrufs von 'iter ()' zurück

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

Ausgabe:

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 
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.