2010-07-16 10 views
89

Kann ich einen Iterator/Generator in Python zurücksetzen? Ich benutze DictReader und möchte es (vom csv-Modul) an den Anfang der Datei zurücksetzen.Können Iteratoren in Python zurückgesetzt werden?

+0

Mögliches Duplikat von [Generierungsobjekt in Python zurücksetzen] (http://stackoverflow.com/questions/1271320/reseting-generator-object-in-python) – sschuberth

Antwort

59

ich viele Antworten sehen was darauf hindeutet, itertools.tee, aber das ist in der Dokumentation für sie eine wichtige Warnung ignorieren:

Diese itertool kann erfordern erhebliche Hilfsspeicher (je nachdem, wie viel temporäre Daten müssen gespeichert werden). Wenn ein Iterator die meisten oder alle Daten verwendet, bevor ein anderer Iterator startet, ist es schneller list() statt tee() zu verwenden.

Grundsätzlich sind tee für jene Situation entwickelt, in denen zwei (oder mehr) Klonen eines Iterators, während sie miteinander „out of sync bekommen“, dies nicht tun durch viele - besser gesagt, sie sagen in der gleichen "Umgebung" (ein paar Dinge hinter oder voreinander). Nicht geeignet für das OP-Problem "Redo von Anfang an".

L = list(DictReader(...)) auf der anderen Seite ist perfekt geeignet, solange die Liste der dicts bequem im Speicher passen kann. Ein neuer "Iterator von Anfang an" (sehr leicht und mit geringem Aufwand) kann jederzeit mit iter(L) erstellt und teilweise oder ganz verwendet werden, ohne dass neue oder bestehende Änderungen betroffen sind. Andere Zugriffsmuster sind ebenfalls leicht verfügbar.

Wie mehrere Antworten richtig angemerkt haben, können Sie im speziellen Fall csv auch das zugrunde liegende Dateiobjekt .seek(0) (ein eher spezieller Fall). Ich bin mir nicht sicher, ob das dokumentiert und garantiert ist, obwohl es momentan funktioniert. Es wäre wahrscheinlich eine Überlegung wert, nur für wirklich große CSV-Dateien, in denen die list Ich empfehle als der allgemeine Ansatz würde einen zu großen Speicherbedarf haben.

+3

Mit 'list()' zum Zwischenspeichern von Multipassage über einen csvreader auf einer 5MB-Datei sieht meine Laufzeit von ~ 12sec zu ~ 0.5s. –

0

Nur wenn der zugrundeliegende Typ einen Mechanismus dafür bereitstellt (z. B. fp.seek(0)).

16

Nein. Das Iteratorprotokoll von Python ist sehr einfach und bietet nur eine einzige Methode (.next() oder __next__()) und keine Methode, um einen Iterator im Allgemeinen zurückzusetzen.

Das allgemeine Muster besteht darin, stattdessen einen neuen Iterator zu erstellen, der dasselbe Verfahren erneut verwendet.

Wenn Sie möchten, zu „retten off“ einen Iterator, so dass Sie wieder an den Anfang gehen können, können Sie auch die Iterator Gabel von itertools.tee

+0

Während Sie Analyse der .next() Methode ist wahrscheinlich richtig, es gibt einen ziemlich einfachen Weg, um zu bekommen, was die OP verlangt. – Wilduck

+0

@Wilduck: Ich sehe deine Antwort. Ich habe gerade die Iterator-Frage beantwortet, und ich habe keine Ahnung vom 'csv'-Modul. Hoffentlich sind beide Antworten nützlich für das Original-Poster. – u0b34a0f6ae

+0

Genau genommen benötigt das Iteratorprotokoll auch '__iter__'. Das heißt, Iteratoren müssen auch iterierbar sein. –

25

Verwendung Wenn Sie eine CSV-Datei ‚blah.csv‘ genannt haben, dass sieht aus wie

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 kann, und eine DictReader mit

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

Dann erstellen, können Sie die nächste Zeile bekommen, reader.next(), die Ausgabe, die es wieder sollte

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

mit

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

an dieser Stelle jedoch produzieren, wenn Sie blah.seek(0) verwenden, können Sie das nächste Mal reader.next() Sie rufen 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 bewusst sind. @Brian schlug vor, einfach einen anderen DictReader zu erstellen. Dies funktioniert nicht, wenn der erste Leser zum ersten Mal die Datei liest, da Ihr neuer Reader über unerwartete Schlüssel und Werte verfügt, unabhängig davon, wo Sie sich in der Datei befinden.

+0

Das hat mir meine Theorie gesagt, schön zu sehen, dass das, was ich dachte, passieren sollte. –

+0

@Wilduck: Das Verhalten, das Sie mit einer anderen Instanz von DictReader beschreiben, wird nicht passieren, wenn Sie eine neue Datei behandeln und diese an den zweiten DictReader übergeben, richtig? – user248237dfsf

+0

Wenn Sie zwei Dateihandler haben, werden sie sich unabhängig verhalten, ja. – Wilduck

2

Während es keinen Iterator Reset gibt, hat das "itertools" Modul von Python 2.6 (und höher) einige Hilfsprogramme, die dort helfen können. Eines davon ist das "tee", das mehrere Kopien eines Iterators erstellen und die Ergebnisse des vorauslaufenden Iterators zwischenspeichern kann, sodass diese Ergebnisse auf den Kopien verwendet werden. Ich werde Ihre Zwecke seve:

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

Es gibt einen Fehler bei der Verwendung von .seek (0), wie von Alex Martelli und Wilduck oben befürwortet, nämlich dass der nächste Aufruf von .next() Ihnen ein Wörterbuch Ihrer Kopfzeile in Form von {key1: key1 , Schlüssel2: Schlüssel2, ...}. Die Umgehung folgt file.seek (0) mit einem Aufruf von reader.next(), um die Kopfzeile loszuwerden.

So würde der Code wie folgt 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) 
6

Ja, wenn Sie numpy.nditer verwenden Ihre Iterator zu bauen.

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

Kann 'nditer' durch das Array wie' itertools.cycle' gehen? – LWZ

+0

@ LWZ: Ich glaube nicht, aber Sie können versuchen: 'the' next() 'und auf eine' StopIteration' Ausnahme machen Sie ein 'reset()'. –

+0

... gefolgt von einem 'next()' –

0

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

Dies ist vielleicht orthogonal zur ursprünglichen Frage, aber man konnte den Iterator in einer Funktion wickeln, 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 die genannte Funktion keine Argumente benötigt.

Falls die Funktion einige Argumente benötigt, verwenden Sie functools.partial, um eine Closure zu erstellen, die 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, die T-Stück (n Kopien) oder Liste (1 Kopie)

0

list(generator()) gibt alle verbleibenden Werte für einen Generator tun müßte zu vermeiden und setzt es effektiv, wenn es nicht geschlungen .

0

Für kleine Dateien können Sie die Verwendung von more_itertools.seekable in Betracht ziehen - ein Tool von Drittanbietern, das iterierbare Rücksetzungen anbietet.

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

Output

{'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 ein DictReader in einem seekable umwickelnde Gegenstand (1) und Advanced (2). Die seek() Methode wird verwendet, um den Iterator an die 0. Position zurückzusetzen/zurückzuspulen (3).

Hinweis: Der Speicherverbrauch wächst mit der Iteration. Seien Sie also vorsichtig, wenn Sie dieses Tool auf große Dateien anwenden, z. B. indicated in the docs.

0

Problem

Ich hatte das gleiche Problem zuvor. Nach dem Analysieren meines Codes wurde mir klar, dass der Versuch, den Iterator innerhalb von Schleifen zurückzusetzen, die Zeitkomplexität leicht erhöht und den Code auch etwas hässlich macht.

Lösung

Öffnen Sie die Datei und die Zeilen in eine Variable im Speicher speichern.

# 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 eine Schleife durch Reihen überall in Ihrem Umfang ohne mit einem Iterator handelt.