¿Puedo restablecer un iterador / generador en Python? Estoy usando DictReader y me gustaría restablecerlo al comienzo del archivo.
¿Puedo restablecer un iterador / generador en Python? Estoy usando DictReader y me gustaría restablecerlo al comienzo del archivo.
Respuestas:
Veo muchas respuestas que sugieren itertools.tee , pero eso ignora una advertencia crucial en los documentos para ello:
Esta herramienta iterativa puede requerir un almacenamiento auxiliar significativo (dependiendo de la cantidad de datos temporales que se deban almacenar). En general, si un iterador usa la mayoría o la totalidad de los datos antes de que comience otro iterador, es más rápido usarlo en
list()
lugar de hacerlotee()
.
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 , más bien, dicen en la misma "vecindad" (un pocos elementos uno detrás 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 dictos pueda caber cómodamente en la memoria. Se puede crear un nuevo "iterador desde el principio" (muy ligero y de bajo costo) en cualquier momento coniter(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 fácilmente disponibles.
Como varias respuestas señalaron correctamente, en el caso específico de csv
usted 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 verdaderamente enormes, en los list
que recomiendo que el enfoque general tenga una huella de memoria demasiado grande.
list()
de caché multipassage a través de un csvreader en un archivo de 5 MB hace que mi tiempo de ejecución pase de ~ 12 segundos a ~ 0.5 s.
Si tiene un archivo csv llamado 'blah.csv', parece que
a,b,c,d
1,2,3,4
2,3,4,5
3,4,5,6
sabes que puedes 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 generar
{'a':1,'b':2,'c':3,'d':4}
usarlo nuevamente 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 reader.next()
obtendrá
{'a':1,'b':2,'c':3,'d':4}
de nuevo.
Esta parece ser la funcionalidad que estás buscando. Sin embargo, estoy seguro de que hay algunos trucos asociados con este enfoque que 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 cualquier lugar del archivo.
No. El protocolo de iterador de Python es muy simple y solo proporciona un único método ( .next()
o __next__()
), y ningún método para restablecer un iterador en general.
El patrón común es, en cambio, crear un nuevo iterador utilizando el mismo procedimiento nuevamente.
Si desea "guardar" un iterador para poder volver a su inicio, también puede bifurcar el iterador utilizando itertools.tee
csv
módulo. Esperemos que ambas respuestas sean útiles para el póster original.
__iter__
. Es decir, los iteradores también deben ser iterables.
Sí , si usas numpy.nditer
para construir tu 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
nditer
recorrer la matriz como itertools.cycle
?
try:
el next()
y en una StopIteration
excepción hacer reset()
.
next()
Hay un error en el uso .seek(0)
como lo recomiendan Alex Martelli y Wilduck anteriormente, a saber, que la próxima llamada a .next()
le dará un diccionario de su fila de encabezado en forma de {key1:key1, key2:key2, ...}
. La solución es seguir file.seek(0)
con una llamada para reader.next()
deshacerse de la fila del encabezado.
Entonces su código se vería así:
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)
Esto es quizás ortogonal a la pregunta original, pero uno podría ajustar el iterador en una función que devuelve el iterador.
def get_iter():
return iterator
Para restablecer el iterador simplemente llame a la función nuevamente. 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 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ían que hacer tee (n copias) o list (1 copia)
Para archivos pequeños, puede considerar usar 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í a DictReader
está envuelto en un seekable
objeto (1) y avanzado (2). losseek()
método se utiliza para restablecer / 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 .
Si bien no hay restablecimiento de iterador, el módulo "itertools" de python 2.6 (y posterior) tiene algunas utilidades que pueden ayudarlo. Uno de ellos es el "tee", que puede hacer múltiples copias de un iterador y almacenar en caché los resultados del que está por delante, para que estos resultados se utilicen en las copias. Cortaré 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 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.
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.
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 las filas en cualquier lugar de su alcance sin tener que lidiar con un iterador.
Una opción posible es usarla itertools.cycle()
, lo que te permitirá iterar indefinidamente sin ningún truco .seek(0)
.
iterDic = itertools.cycle(csv.DictReader(open('file.csv')))
Estoy llegando a este mismo problema, aunque me gusta el tee()
solución, no sé qué tan grandes van a ser mis archivos y las advertencias de memoria acerca de consumir uno primero antes de que el otro me desanime a adoptar ese método.
En cambio, estoy creando un par de iteradores usando iter()
declaraciones, y usando el primero para mi ejecución inicial, antes de cambiar al segundo para la ejecución final.
Entonces, en el caso de un dict-reader, si el lector se define usando:
d = csv.DictReader(f, delimiter=",")
Puedo crear un par de iteradores a partir de esta "especificación", utilizando:
d1, d2 = iter(d), iter(d)
Entonces puedo ejecutar mi código de primer paso contra d1
, seguro sabiendo que el segundo iterador d2
se ha definido a partir de la misma especificación raíz.
No lo he probado exhaustivamente, pero parece funcionar con datos ficticios.
Solo si el tipo subyacente proporciona un mecanismo para hacerlo (por ejemplo fp.seek(0)
).
Devuelve un iterador recién creado en la última iteración durante la llamada '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=' ')
Salida:
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