¿Se pueden restablecer los iteradores en Python?

Respuestas:

84

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 hacerlo tee().

Básicamente, teeestá 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 csvusted 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 listque recomiendo que el enfoque general tenga una huella de memoria demasiado grande.

Alex Martelli
fuente
66
El uso 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.
John Mee el
33

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.

Wilduck
fuente
Esto fue lo que me dijo mi teoría, es bueno ver que lo que pensé que debería pasar, sí.
Wayne Werner
@Wilduck: el comportamiento que está describiendo con otra instancia de DictReader no sucederá si crea un nuevo identificador de archivo y lo pasa al segundo DictReader, ¿verdad?
Si tiene dos manejadores de archivos, se comportarán independientemente, sí.
Wilduck
24

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

u0b34a0f6ae
fuente
1
Si bien el análisis del método .next () es probablemente correcto, hay una forma bastante simple de obtener lo que está pidiendo el operador.
Wilduck
2
@Wilduck: veo que tu respuesta. Acabo de responder la pregunta del iterador, y no tengo idea sobre el csvmódulo. Esperemos que ambas respuestas sean útiles para el póster original.
u0b34a0f6ae
Estrictamente, el protocolo iterador también requiere __iter__. Es decir, los iteradores también deben ser iterables.
Steve Jessop
11

, si usas numpy.nditerpara 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
Desarrollador
fuente
¿Puede nditerrecorrer la matriz como itertools.cycle?
LWZ
1
@LWZ: Yo no lo creo, pero puedo try:el next()y en una StopIterationexcepción hacer reset().
Pausado hasta nuevo aviso.
... seguido de unnext()
Pausado hasta nuevo aviso.
Esto es lo que estaba buscando !
Sriram
1
Tenga en cuenta que el límite de "operandos" aquí es 32: stackoverflow.com/questions/51856685/…
Simon
11

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)
Steven Rumbalski
fuente
5

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)

Anish
fuente
3

Para archivos pequeños, puede considerar usar more_itertools.seekableuna 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 DictReaderestá envuelto en un seekableobjeto (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 .

pylang
fuente
2

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]
jsbueno
fuente
1

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()
mAsT3RpEE
fuente
1

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

Will Dereham
fuente
1

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 las filas en cualquier lugar de su alcance sin tener que lidiar con un iterador.

Anthony Holloman
fuente
1

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')))
Greg H
fuente
1

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 d2se ha definido a partir de la misma especificación raíz.

No lo he probado exhaustivamente, pero parece funcionar con datos ficticios.

Thomas Kimber
fuente
0

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

Ignacio Vazquez-Abrams
fuente
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 
enojado
fuente