Gli iteratori possono essere ripristinati in Python?


130

Posso ripristinare un iteratore / generatore in Python? Sto usando DictReader e vorrei ripristinarlo all'inizio del file.


1

In una nota a margine, ho scoperto che la list()funzione itererà attraverso il suo argomento (un iterabile). Quindi invocando list()due volte lo stesso iterabile (es. Risultato di zip()) otterrai un elenco vuoto alla seconda chiamata!
theaws.blog

Risposte:


84

Vedo molte risposte che suggeriscono itertools.tee , ma questo ignora un avvertimento cruciale nei documenti per questo:

Questo itertool può richiedere una notevole memoria ausiliaria (a seconda della quantità di dati temporanei che devono essere archiviati). In generale, se un iteratore utilizza la maggior parte o tutti i dati prima dell'avvio di un altro iteratore, è più veloce da usare list()invece di tee().

Fondamentalmente, teeè progettato per quelle situazioni in cui due (o più) cloni di un iteratore, mentre "perdono la sincronizzazione" l'uno con l'altro, non lo fanno di molto - piuttosto, dicono nella stessa "vicinanza" (un pochi elementi uno dietro o davanti all'altro). Non adatto al problema dell'OP di "rifare dall'inizio".

L = list(DictReader(...))d'altra parte è perfettamente adatto, purché l'elenco dei dict possa stare comodamente nella memoria. Un nuovo "iteratore dall'inizio" (molto leggero e basso overhead) può essere realizzato in qualsiasi momento iter(L)e utilizzato in parte o per intero senza influenzare quelli nuovi o esistenti; sono facilmente disponibili anche altri modelli di accesso.

Come diverse risposte hanno giustamente sottolineato, nel tuo caso specifico csvpuoi anche .seek(0)l'oggetto file sottostante (un caso piuttosto speciale). Non sono sicuro che sia documentato e garantito, anche se attualmente funziona; probabilmente varrebbe la pena considerare solo per file csv veramente enormi, in cui lo listconsiglio in quanto l'approccio generale avrebbe un'impronta di memoria troppo grande.


6
L'utilizzo list()della cache multipassage su un csvreader su un file da 5 MB vede il mio runtime passare da ~ 12 secondi a ~ 0,5 secondi.
John Mee

33

Se hai un file CSV denominato "blah.csv", questo è simile a

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

sai che puoi aprire il file per la lettura e creare un DictReader con

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

Quindi, sarai in grado di ottenere la riga successiva con reader.next(), che dovrebbe essere visualizzata

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

usandolo di nuovo produrrà

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

Tuttavia, a questo punto se usi blah.seek(0), la prossima volta che chiamerai reader.next()riceverai

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

ancora.

Questa sembra essere la funzionalità che stai cercando. Sono sicuro che ci sono alcuni trucchi associati a questo approccio di cui tuttavia non sono a conoscenza. @Brian ha suggerito semplicemente di creare un altro DictReader. Questo non funzionerà se il tuo primo lettore è a metà della lettura del file, poiché il tuo nuovo lettore avrà chiavi e valori imprevisti da qualsiasi punto del file.


Questo era ciò che mi diceva la mia teoria, bello vedere che ciò che pensavo dovesse accadere, fa.
Wayne Werner

@ Wilduck: il comportamento che stai descrivendo con un'altra istanza di DictReader non si verificherà se crei un nuovo handle di file e lo passi al secondo DictReader, giusto?

Se hai due gestori di file, si comporteranno in modo indipendente, sì.
Wilduck

24

No. Il protocollo iteratore di Python è molto semplice e fornisce solo un singolo metodo ( .next()o __next__()) e nessun metodo per ripristinare un iteratore in generale.

Lo schema comune è invece creare un nuovo iteratore usando di nuovo la stessa procedura.

Se vuoi "salvare" un iteratore in modo da poter tornare all'inizio, puoi anche eseguire il fork dell'iteratore usando itertools.tee


1
Sebbene l'analisi del metodo .next () sia probabilmente corretta, esiste un modo abbastanza semplice per ottenere ciò che l'operazione richiede.
Wilduck

2
@ Wilduck: vedo che la tua risposta. Ho appena risposto alla domanda dell'iteratore e non ho idea del csvmodulo. Si spera che entrambe le risposte siano utili al poster originale.
u0b34a0f6ae

In senso stretto, richiede anche il protocollo iteratore __iter__. Cioè, anche gli iteratori devono essere iterabili.
Steve Jessop

11

, se usi numpy.nditerper costruire il tuo iteratore.

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

Può nditerscorrere l'array come itertools.cycle?
LWZ

1
@LWZ: Io non la penso così, ma è possibile try:l' next()e su StopIterationun'eccezione fare reset().
In pausa fino a nuovo avviso.

... seguito da unnext()
In pausa fino a nuovo avviso.

Questo è quello che stavo cercando!
sriram

1
Nota che il limite di "operandi" qui è 32: stackoverflow.com/questions/51856685/…
Simon

11

C'è un bug nell'uso .seek(0)come sostenuto da Alex Martelli e Wilduck sopra, vale a dire che la prossima chiamata a .next()ti darà un dizionario della tua riga di intestazione sotto forma di {key1:key1, key2:key2, ...}. Il problema è seguire file.seek(0)con una chiamata a reader.next()per eliminare la riga di intestazione.

Quindi il tuo codice sarebbe simile a questo:

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

Questo è forse ortogonale alla domanda originale, ma si potrebbe racchiudere l'iteratore in una funzione che restituisca l'iteratore.

def get_iter():
    return iterator

Per ripristinare l'iteratore basta chiamare di nuovo la funzione. Questo è ovviamente banale se la funzione quando detta funzione non accetta argomenti.

Nel caso in cui la funzione richieda alcuni argomenti, utilizzare functools.partial per creare una chiusura che può essere passata al posto dell'iteratore originale.

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

Questo sembra evitare il caching che tee (n copie) o list (1 copia) dovrebbero fare


3

Per file di piccole dimensioni, potresti prendere in considerazione l'utilizzo di more_itertools.seekableuno strumento di terze parti che offre il ripristino degli iterabili.

dimostrazione

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

Produzione

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

Qui a DictReaderè avvolto in un seekableoggetto (1) e avanzato (2). Il seek()metodo viene utilizzato per ripristinare / riavvolgere l'iteratore alla posizione 0 (3).

Nota: il consumo di memoria aumenta con l'iterazione, quindi fai attenzione quando applichi questo strumento a file di grandi dimensioni, come indicato nei documenti .


2

Sebbene non vi sia alcun ripristino dell'iteratore, il modulo "itertools" da python 2.6 (e successivi) ha alcune utilità che possono essere d'aiuto. Uno di questi è il "tee" che può fare più copie di un iteratore e memorizzare nella cache i risultati di quello in esecuzione, in modo che questi risultati vengano usati sulle copie. Vedrò i tuoi scopi:

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

Per DictReader:

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

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

Per 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()) restituisce tutti i valori rimanenti per un generatore e lo ripristina efficacemente se non è in loop.


1

Problema

Ho già avuto lo stesso problema. Dopo aver analizzato il mio codice, mi sono reso conto che il tentativo di reimpostare l'iteratore all'interno dei loop aumenta leggermente la complessità temporale e rende anche il codice un po 'brutto.

Soluzione

Apri il file e salva le righe in una variabile in memoria.

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

Ora puoi scorrere le righe in qualsiasi punto del tuo ambito senza dover gestire un iteratore.


1

Un'opzione possibile è usare itertools.cycle(), che ti permetterà di iterare indefinitamente senza alcun trucco come .seek(0).

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

1

Sto arrivando allo stesso problema: sebbene mi piaccia la tee()soluzione, non so quanto saranno grandi i miei file e gli avvisi di memoria sul consumo uno prima dell'altro mi impediscono di adottare quel metodo.

Invece, sto creando un paio di iteratori usando iter()istruzioni e usando il primo per il mio passaggio iniziale, prima di passare al secondo per l'esecuzione finale.

Quindi, nel caso di un lettore di dict, se il lettore è definito utilizzando:

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

Posso creare un paio di iteratori da questa "specifica" - usando:

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

Posso quindi eseguire il mio codice di primo passaggio d1, sapendo che il secondo iteratore d2è stato definito dalla stessa specifica di root.

Non l'ho testato in modo esaustivo, ma sembra funzionare con dati fittizi.



0

Restituisce un iteratore appena creato all'ultima iterazione durante la chiamata '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=' ')

Produzione:

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 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.