¿Se pueden restablecer los iteradores en Python?

¿Puedo reiniciar un iterador / generador en Python? Estoy usando DictReader y me gustaría restablecerlo (desde el módulo csv) al principio del archivo.

Veo muchas respuestas que sugieren itertools.tee , pero eso es ignorar una advertencia crucial en los documentos:

Esta herramienta puede requerir un importante almacenamiento auxiliar (dependiendo de la cantidad de datos temporales que deban almacenarse). En general, si un iterador usa la mayoría o todos los datos antes de que comience otro iterador, es más rápido usar list() lugar de tee() .

Básicamente, tee está diseñado para aquellas situaciones en las que dos (o más) clones de un iterador, mientras se “desincronizan” entre sí, no lo hacen por mucho , sino que dicen en la misma “vecindad” ( algunos artículos detrás o delante 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 usarlo en parte o en su totalidad sin afectar a los nuevos o existentes; otros patrones de acceso también son fácilmente disponibles.

Como varias respuestas acertadamente comentaron, 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 valdría la pena considerarlo solo para archivos csv realmente enormes, en los que la list que recomiendo como enfoque general tendría una huella de memoria demasiado grande.

Si tiene un archivo csv llamado ‘blah.csv’ que parece

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

sabe que puede abrir el archivo para leerlo y crear un DictReader con

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

Luego, podrá obtener la siguiente línea con reader.next() , que debería mostrar

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

usándolo de nuevo producirá

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

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

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

otra vez.

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

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

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

Si desea “salvar” un iterador para que pueda volver a su principio, también puede bifurcar el iterador usando itertools.tee

Hay un error en el uso de .seek (0) tal como lo recomiendan Alex Martelli y Wilduck, a saber, que la próxima llamada a .next () le dará un diccionario de su fila de encabezado en la forma de {key1: key1, key2: key2 , …}. La solución es seguir file.seek (0) con una llamada a reader.next () para deshacerse de la fila del encabezado.

Entonces tu código se vería 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) 

, si usa numpy.nditer para construir su 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 

Esto es quizás ortogonal a la pregunta original, pero uno podría ajustar el iterador en una función que devuelva el iterador.

 def get_iter(): return iterator 

Para restablecer el iterador, simplemente vuelva a llamar a la función. Esto es, por supuesto, trivial si la función cuando dicha función no tiene argumentos.

En el caso de que la función requiera algunos argumentos, use functools.partial para crear un cierre que se pueda 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 tendría que hacer tee (n copias) o lista (1 copia)

Si bien no hay reinicio de iterador, el módulo “itertools” de python 2.6 (y versiones posteriores) tiene algunas utilidades que pueden ayudarlo allí. Uno de ellos es el “tee”, que puede hacer varias copias de un iterador, y almacenar en caché los resultados del que está por delante, para que estos resultados se utilicen en las copias. Cumpliré tus 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] 

Para archivos pequeños, puede considerar el uso de more_itertools.seekable , una herramienta de terceros que ofrece restablecer iterables.

Manifestació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 de seekable (1) y avanzado (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 se indica en los documentos .

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

Abra el archivo y guarde las filas en 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 en cualquier parte de su scope sin tener que lidiar con un iterador.

Solo si el tipo subyacente proporciona un mecanismo para hacerlo (por ejemplo, fp.seek(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() 

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

La opción posible es usar itertools.cycle () que permitirá iterar indefinidamente sin ningún truco como .seek (0)

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