2010-07-16 11 views
89

¿Puedo restablecer un iterador/generador en Python? Estoy usando DictReader y me gustaría restablecerlo (desde el módulo csv) al comienzo del archivo.¿Se pueden restablecer los iteradores en Python?

+0

Posible duplicado de [Restablecimiento del objeto del generador en Python] (http://stackoverflow.com/questions/1271320/reseting-generator-object-in-python) – sschuberth

Respuesta

59

veo muchas respuestas que sugieren itertools.tee, pero eso es ignorar una advertencia crucial en la documentación para ello:

Este itertool pueden requerir significativa almacenamiento auxiliar (dependiendo de cómo se deben almacenar muchos datos temporales ). En general, si un iterador utiliza la mayoría o todos los datos antes de se inicia otro iterador, es más rápido para usar list() en lugar de tee().

Básicamente, tee está diseñado para aquellos situación en la que dos (o más) de clones de uno iterador, mientras que "salir de sincronización" entre sí, no lo hacen, lo hacen por tanto - más bien, decir en la misma "vecindad" (algunos elementos detrás o delante uno del otro). No es adecuado para el problema del OP de "rehacer desde el principio".

L = list(DictReader(...)) por otro lado es perfectamente adecuado, siempre y cuando la lista de dicts pueda caber cómodamente en la memoria. Se puede hacer un nuevo "iterador desde el principio" (muy liviano y de bajo costo) en cualquier momento con iter(L), y se puede usar en parte o en su totalidad sin afectar a los nuevos o existentes; otros patrones de acceso también están disponibles fácilmente.

Como varias de las respuestas indicadas correctamente, en el caso específico de csv también puede .seek(0) el objeto de archivo subyacente (un caso bastante especial). No estoy seguro de que esté documentado y garantizado, aunque actualmente funciona; Probablemente valga la pena considerar solo para archivos csv realmente grandes, en los cuales recomiendo el list ya que el enfoque general tendría una huella de memoria demasiado grande.

+3

El uso de 'list()' para almacenar en caché multipassage sobre un csvreader en un archivo de 5MB ve mi tiempo de ejecución entre ~ 12secs y ~ 0.5s. –

0

Solo si el tipo subyacente proporciona un mecanismo para hacerlo (por ejemplo, fp.seek(0)).

16

No. El protocolo de iterador de Python es muy simple, y solo proporciona un único método (.next() o __next__()), y no hay ningún método para reiniciar un iterador en general.

El patrón común es crear un nuevo iterador usando el mismo procedimiento nuevamente.

Si usted quiere "salvar a fuera" un iterador para que pueda volver a su principio, también se puede bifurcar el iterador utilizando itertools.tee

+0

Mientras está analizando el método .next() es probablemente sea correcto, hay una manera bastante simple de obtener lo que el operador está pidiendo. – Wilduck

+0

@Wilduck: veo tu respuesta. Acabo de responder la pregunta del iterador, y no tengo idea sobre el módulo 'csv'. Esperemos que ambas respuestas sean útiles para el póster original. – u0b34a0f6ae

+0

Estrictamente, el protocolo de iterador también requiere '__iter__'. Es decir, los iteradores también son iterables. –

25

Si usted tiene un archivo CSV denominado 'blah.csv' Eso parece

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

usted sabe que puede abrir el archivo para lectura, y crear un DictReader con

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

Entonces, usted será capaz de obtener la siguiente línea con reader.next(), que debe de salida

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

de volver a utilizarlo producirá

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

Sin embargo, en este punto si utiliza blah.seek(0), la próxima vez que llame reader.next() obtendrá

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

de nuevo.

Esta parece ser la funcionalidad que está buscando. Estoy seguro de que hay algunos trucos asociados con este enfoque que de todos modos no conozco. @Brian sugirió simplemente crear otro DictReader. Esto no funcionará si su primer lector está a la mitad de la lectura del archivo, ya que su nuevo lector tendrá claves y valores inesperados desde donde se encuentre en el archivo.

+0

Esto fue lo que mi teoría me dijo, es agradable ver que lo que pensé que debería suceder, sí. –

+0

@Wilduck: el comportamiento que está describiendo con otra instancia de DictReader no sucederá si crea un nuevo manejador de archivo y lo pasa al segundo DictReader, ¿verdad? – user248237dfsf

+0

Si tiene dos manejadores de archivos, se comportarán de forma independiente, sí. – Wilduck

2

Si bien no hay restablecimiento de iterador, el módulo "itertools" de python 2.6 (y posterior) tiene algunas utilidades que pueden ayudar. Uno de ellos es la "T" que puede hacer copias múltiples de un iterador, y almacenar en caché los resultados del que se está ejecutando, para que estos resultados se utilicen en las copias. Voy a seve sus propósitos:

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

Hay un error al usar .seek (0) como abogó por Alex Martelli y Wilduck anteriormente, es decir, que la siguiente llamada a .next() le dará un diccionario de su fila de encabezado en forma de {key1: key1 , tecla2: tecla2, ...}. El resto es seguir file.seek (0) con una llamada a reader.next() para deshacerse de la fila del encabezado.

Así que su código sería algo como esto:

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

, si se utiliza para construir su numpy.nditer iterador.

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

¿Puede 'nditer' circular a través de la matriz como' itertools.cycle'? – LWZ

+0

@LWZ: No lo creo, pero puede 'intentar:' the 'next()' y en una excepción 'StopIteration' hacer' reset() '. –

+0

... seguido de un 'next()' –

0

Para DictReader:

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

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

Para 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

Ésta es quizás ortogonal a la pregunta original, pero se podría envolver el iterador en una función que devuelve el iterador.

def get_iter(): 
    return iterator 

Para restablecer el iterador, simplemente llame de nuevo a la función. Esto es por supuesto trivial si la función cuando dicha función no toma argumentos.

En el caso de que la función requiera algunos argumentos, use functools.partial para crear un cierre que se puede pasar en lugar del iterador original.

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

Esto parece evitar el almacenamiento en caché que la ETE (n copias) o la lista (1 copia) tendría que hacer

0

list(generator()) devuelve todos los valores restantes para un generador y efectivamente restablece si no se conecta en bucle .

0

Para archivos pequeños, puede considerar el uso de more_itertools.seekable - una herramienta de terceros que ofrece la restauración de iterables.

Demostración

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

salida

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

Aquí un DictReader se envuelve en un objeto seekable (1) y avanzada (2). El método seek() se usa para reiniciar/rebobinar el iterador a la posición 0 (3).

Nota: el consumo de memoria aumenta con la iteración, así que tenga cuidado al aplicar esta herramienta a archivos grandes, como indicated in the docs.

0

Problema

He tenido el mismo problema antes. Después de analizar mi código, me di cuenta de que intentar restablecer el iterador dentro de los bucles aumenta ligeramente la complejidad del tiempo y también hace que el código sea un poco feo.

Solución

Abrir el archivo y guardar las filas de una variable en la 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) 

Ahora puede recorrer filas cualquier lugar de su alcance sin tener que lidiar con un iterador.

Cuestiones relacionadas