type que iteradores instruction generadores generador creacion python generator yield

que - yield instruction python



Restablecer el objeto del generador en Python (14)

Ahora puede usar more_itertools.seekable (una herramienta de terceros) que permite restablecer los iteradores.

Instalar a través de > pip install more_itertools

import more_itertools as mit y = mit.seekable(FunctionWithYield()) for x in y: print(x) y.seek(0) # reset iterator for x in y: print(x)

Nota: el consumo de memoria aumenta al avanzar el iterador, así que tenga cuidado con los iterables grandes.

Tengo objeto generador devuelto por rendimiento múltiple. La preparación para llamar a este generador es una operación bastante lenta. Es por eso que quiero reutilizar el generador varias veces.

y = FunctionWithYield() for x in y: print(x) #here must be something to reset ''y'' for x in y: print(x)

Por supuesto, estoy pensando en copiar el contenido en una lista simple.


De la documentación oficial de tee :

En general, si un iterador usa la mayoría o la totalidad de los datos antes de que se inicie otro iterador, es más rápido usar list () en lugar de tee ().

Por lo tanto, es mejor utilizar la list(iterable) en su caso.


Los generadores no pueden rebobinarse. Usted tiene las siguientes opciones:

  1. Ejecute de nuevo la función del generador, reiniciando la generación:

    y = FunctionWithYield() for x in y: print(x) y = FunctionWithYield() for x in y: print(x)

  2. Almacene los resultados del generador en una estructura de datos en la memoria o el disco que puede repetir una vez más:

    y = list(FunctionWithYield()) for x in y: print(x) # can iterate again: for x in y: print(x)

La desventaja de la opción 1 es que computa los valores nuevamente. Si eso consume mucha CPU, terminas calculando dos veces. Por otro lado, la desventaja de 2 es el almacenamiento. La lista completa de valores se almacenará en la memoria. Si hay demasiados valores, eso puede ser poco práctico.

Entonces tienes la clásica compensación de memoria vs. procesamiento . No puedo imaginar una forma de rebobinar el generador sin almacenar los valores o calcularlos de nuevo.


No estoy seguro de lo que quieres decir con una preparación costosa, pero supongo que en realidad tienes

data = ... # Expensive computation y = FunctionWithYield(data) for x in y: print(x) #here must be something to reset ''y'' # this is expensive - data = ... # Expensive computation # y = FunctionWithYield(data) for x in y: print(x)

Si ese es el caso, ¿por qué no reutilizar los data ?


No hay opción para restablecer iteradores. El iterador generalmente aparece cuando itera a través de la función next() . La única forma es tomar una copia de seguridad antes de iterar en el objeto iterador. Ver abajo

Creación de un objeto iterador con los elementos del 0 al 9

i=iter(range(10))

Iterando a través de la función next () que saldrá

print(next(i))

Conversión del objeto iterador a la lista

L=list(i) print(L) output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

así que el elemento 0 ya apareció. También todos los elementos aparecen cuando convertimos el iterador a la lista.

next(L) Traceback (most recent call last): File "<pyshell#129>", line 1, in <module> next(L) StopIteration

Por lo tanto, debe convertir el iterador en listas de copia de seguridad antes de comenzar la iteración. La lista se puede convertir a iterador con iter(<list-object>)


Ok, dices que quieres llamar a un generador varias veces, pero la inicialización es costosa ... ¿Qué tal algo así?

class InitializedFunctionWithYield(object): def __init__(self): # do expensive initialization self.start = 5 def __call__(self, *args, **kwargs): # do cheap iteration for i in xrange(5): yield self.start + i y = InitializedFunctionWithYield() for x in y(): print x for x in y(): print x

De forma alternativa, puede crear su propia clase que siga el protocolo del iterador y defina algún tipo de función de "reinicio".

class MyIterator(object): def __init__(self): self.reset() def reset(self): self.i = 5 def __iter__(self): return self def next(self): i = self.i if i > 0: self.i -= 1 return i else: raise StopIteration() my_iterator = MyIterator() for x in my_iterator: print x print ''resetting...'' my_iterator.reset() for x in my_iterator: print x

https://docs.python.org/2/library/stdtypes.html#iterator-types http://anandology.com/python-practice-book/iterators.html


Otra opción es usar la función itertools.tee() para crear una segunda versión de tu generador:

y = FunctionWithYield() y, y_backup = tee(y) for x in y: print(x) for x in y_backup: print(x)

Esto podría ser beneficioso desde el punto de vista del uso de la memoria si la iteración original podría no procesar todos los elementos.


Probablemente la solución más simple es envolver la parte costosa en un objeto y pasarlo al generador:

data = ExpensiveSetup() for x in FunctionWithYield(data): pass for x in FunctionWithYield(data): pass

De esta manera, puede almacenar en caché los costosos cálculos.

Si puede mantener todos los resultados en la RAM al mismo tiempo, utilice list() para materializar los resultados del generador en una lista simple y trabaje con eso.


Puede definir una función que devuelva su generador

def f(): def FunctionWithYield(generator_args): code here... return FunctionWithYield

Ahora puedes hacer tantas veces como quieras:

for x in f()(generator_args): print(x) for x in f()(generator_args): print(x)


Quiero ofrecer una solución diferente a un viejo problema

class IterableAdapter: def __init__(self, iterator_factory): self.iterator_factory = iterator_factory def __iter__(self): return self.iterator_factory() squares = IterableAdapter(lambda: (x * x for x in range(5))) for x in squares: print(x) for x in squares: print(x)

El beneficio de esto cuando se compara con algo como list(iterator) es que esta es O(1) complejidad del espacio y la list(iterator) es O(n) . La desventaja es que, si solo tiene acceso al iterador, pero no a la función que produjo el iterador, entonces no puede usar este método. Por ejemplo, podría parecer razonable hacer lo siguiente, pero no funcionará.

g = (x * x for x in range(5)) squares = IterableAdapter(lambda: g) for x in squares: print(x) for x in squares: print(x)


Se puede hacer por objeto de código. Aquí está el ejemplo.

code_str="y=(a for a in [1,2,3,4])" code1=compile(code_str,''<string>'',''single'') exec(code1) for i in y: print i

1 2 3 4

for i in y: print i exec(code1) for i in y: print i

1 2 3 4


Si la respuesta de GrzegorzOledzki no es suficiente, probablemente puedas usar send() para lograr tu objetivo. Ver PEP-0342 para más detalles sobre generadores mejorados y expresiones de rendimiento.

ACTUALIZACIÓN: También vea docs.python.org/library/itertools.html#itertools.tee . Implica parte de la compensación de memoria frente a procesamiento mencionada anteriormente, pero podría ahorrar algo de memoria en vez de almacenar los resultados del generador en una list ; depende de cómo uses el generador.


Si su generador es puro en el sentido de que su salida solo depende de los argumentos pasados ​​y del número del paso, y desea que el generador resultante sea reiniciable, aquí hay un fragmento de orden que podría ser útil:

import copy def generator(i): yield from range(i) g = generator(10) print(list(g)) print(list(g)) class GeneratorRestartHandler(object): def __init__(self, gen_func, argv, kwargv): self.gen_func = gen_func self.argv = copy.copy(argv) self.kwargv = copy.copy(kwargv) self.local_copy = iter(self) def __iter__(self): return self.gen_func(*self.argv, **self.kwargv) def __next__(self): return next(self.local_copy) def restartable(g_func: callable) -> callable: def tmp(*argv, **kwargv): return GeneratorRestartHandler(g_func, argv, kwargv) return tmp @restartable def generator2(i): yield from range(i) g = generator2(10) print(next(g)) print(list(g)) print(list(g)) print(next(g))

productos:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [] 0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1


>>> def gen(): ... def init(): ... return 0 ... i = init() ... while True: ... val = (yield i) ... if val==''restart'': ... i = init() ... else: ... i += 1 >>> g = gen() >>> g.next() 0 >>> g.next() 1 >>> g.next() 2 >>> g.next() 3 >>> g.send(''restart'') 0 >>> g.next() 1 >>> g.next() 2