wake-up-neo.com

Können Iteratoren in Python zurückgesetzt werden?

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

101
user248237dfsf

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

Dieses Werkzeug benötigt möglicherweise signifikante Zusatzspeicher (abhängig davon, wie viele temporäre Daten gespeichert werden müssen). Im Allgemeinen, wenn ein Iterator verwendet die meisten oder alle Daten vor ein anderer Iterator startet, es ist schneller Verwenden Sie list() anstelle von tee().

Grundsätzlich ist tee für Situationen gedacht, in denen zwei (oder mehr) Klone eines Iterators, die sich "aus der Synchronisation bringen", sich nicht gegenseitig durch viel tun - vielmehr sagen sie im selben "Nähe" (einige Elemente hinter oder voreinander). Nicht geeignet für das OP-Problem "Wiederholen von Anfang an".

L = list(DictReader(...)) ist dagegen perfekt geeignet, solange die Liste der Diktiere bequem in Erinnerung bleiben kann. Ein neuer "Iterator von Anfang an" (sehr leicht und mit geringem Overhead) kann jederzeit mit iter(L) erstellt und ganz oder teilweise verwendet werden, ohne dass neue oder vorhandene beeinträchtigt werden. Andere Zugriffsmuster sind ebenfalls leicht verfügbar.

Wie einige Antworten zu Recht bemerkt haben, können Sie im speziellen Fall von csv auch das zugrunde liegende Dateiobjekt .seek(0) (ein eher spezieller Fall) verwenden. Ich bin nicht sicher, ob dies dokumentiert und garantiert ist, obwohl es derzeit funktioniert. Es wäre wahrscheinlich nur für wirklich große CSV-Dateien eine Überlegung wert, in denen die list, die ich als generellen Ansatz empfehle, einen zu großen Speicherbedarf hätte.

67
Alex Martelli

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 einen DictReader mit erstellen können

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

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

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

wenn Sie es erneut verwenden, wird es produziert

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

Wenn Sie jedoch blah.seek(0) verwenden, werden Sie beim nächsten Aufruf von reader.next() angezeigt

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

nochmal.

Dies scheint die Funktionalität zu sein, nach der Sie suchen. Ich bin sicher, es gibt einige Tricks, die mit diesem Ansatz verbunden sind, die mir jedoch nicht bewusst sind. @Brian schlug vor, einfach einen anderen DictReader zu erstellen. Dies funktioniert nicht, wenn Sie zum ersten Mal die Datei gelesen haben, da Ihr neuer Leser unerwartete Schlüssel und Werte hat, unabhängig davon, wo Sie sich in der Datei befinden.

29
Wilduck

Nein. Das Iterator-Protokoll 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 einen neuen Iterator mit derselben Prozedur erneut zu erstellen.

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

21
u0b34a0f6ae

Es gibt einen Fehler in der Verwendung von .seek (0), wie er von Alex Martelli und Wilduck oben befürwortet wurde. Der nächste Aufruf von .next () gibt Ihnen ein Wörterbuch Ihrer Kopfzeile in Form von {key1: key1, key2: key2 , ...}. Um dies zu umgehen, folgen Sie file.seek (0) mit einem Aufruf von reader.next (), um die Kopfzeile zu löschen.

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

Ja , wenn Sie numpy.nditer zum Erstellen Ihres Iterators verwenden. 

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

Dies ist möglicherweise 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 die Funktion einfach erneut auf. Dies ist natürlich trivial, wenn die Funktion, wenn die Funktion keine Argumente annimmt.

Falls die Funktion einige Argumente erfordert, erstellen Sie mithilfe von functools.partial eine Schließung, 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 Zwischenspeichern zu vermeiden, das ein Abschlag (n Kopien) oder eine Liste (1 Kopie) erfordern würde

3
Anish

Während es keinen Iterator-Reset 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", das mehrere Kopien eines Iterators erstellen und den Cache zwischenspeichern kann Ergebnisse des vorlaufenden, so dass diese Ergebnisse in den Kopien verwendet werden. Ich werde deine Absichten sehen:

>>> 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]
2
jsbueno

Für kleine Dateien können Sie more_itertools.seekable - ein Drittanbieter-Tool in Betracht ziehen, das das Zurücksetzen von iterable bietet.

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 wird eine DictReader in ein seekable-Objekt (1) und ein erweitertes Objekt (2) eingeschlossen. Mit der Methode seek() wird der Iterator auf die 0-te Position zurückgesetzt (3).

Hinweis: Der Speicherverbrauch wächst mit der Iteration. Daher ist es ratsam, dieses Tool auf große Dateien anzuwenden, wie in angegeben in den Dokumenten .

1
pylang

Problem

Ich hatte das gleiche Problem schon einmal. Nachdem ich meinen Code analysiert hatte, wurde mir klar, dass der Versuch, den Iterator innerhalb von Schleifen zurückzusetzen, die Komplexität der Zeit etwas 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 durch Zeilen eine beliebige Stelle in Ihrem Gültigkeitsbereich durchlaufen, ohne sich mit einem Iterator zu befassen.

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()
0
mAsT3RpEE

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

Mögliche Option ist die Verwendung von itertools.cycle () , die eine unbegrenzte Wiederholung ohne Trick wie .seek (0) ermöglicht

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

list(generator()) gibt alle verbleibenden Werte für einen Generator zurück und setzt ihn zurück, wenn er nicht geloopt wird.

0
Theoremiser