Pregunta

He generador objeto devuelto por múltiples rendimiento.Preparación para llamar a este generador es bastante tiempo de operación.Es por eso que quiero reutilizar 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 tomando en cuenta la copia del contenido en una simple lista.

¿Fue útil?

Solución

Otra opción es utilizar la función itertools.tee() para crear una segunda versión de su 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 de uso de memoria si la iteración original podría no procesar todos los elementos.

Otros consejos

Los generadores no se pueden rebobinar. Dispone de las siguientes opciones:

  1. Ejecutar la función de generador de nuevo, reiniciar la generación:

    y = FunctionWithYield()
    for x in y: print(x)
    y = FunctionWithYield()
    for x in y: print(x)
    
  2. Guarde los resultados de generadores en una estructura de datos en la memoria o en el disco que se puede repetir de nuevo:

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

La desventaja de opción 1 es que calcula los valores de nuevo. Si eso es intensivo de la CPU se termina el cálculo de dos veces. Por otro lado, la desventaja de 2 es el almacenamiento. La lista completa de los valores será almacenada en la memoria. Si hay demasiados valores, que puede ser poco práctico.

Así que tienes el clásico memoria vs compensación procesamiento . No me puedo imaginar una forma de rebobinado del generador sin que ninguna de almacenar los valores o calcular de nuevo.

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

Probablemente la solución más simple es para envolver la parte cara en un objeto y pasar que al generador:

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

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

Si usted puede mantener todos los resultados en la memoria RAM, al mismo tiempo, a continuación, utilizar list() para materializar los resultados del generador en una lista simple y trabajar con eso.

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 así como list(iterator) es que esto es O(1) complejidad espacial y list(iterator) es O(n). La desventaja es que, si sólo tiene acceso al repetidor, pero no la función que produjo el iterador, entonces usted no puede utilizar este método. Por ejemplo, podría parecer razonable para hacer lo siguiente, pero no va a 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)

Si no es suficiente la respuesta de GrzegorzOledzki, probablemente podría utilizar send() para lograr su objetivo. Ver PEP-0342 para más detalles sobre los generadores mejoradas y expresiones de rendimiento.

ACTUALIZACIÓN: También vea itertools.tee() . Se trata de algunos de que la memoria vs compensación procesamiento mencionado anteriormente, pero podría ahorrar algo de memoria más de simplemente almacenar los resultados de generadores en un list; que depende de cómo se está utilizando el generador.

Si el generador es pura en el sentido de que su salida sólo depende de los argumentos pasados y el número de paso, y desea que el resultado generador ser reiniciables, aquí un fragmento de código que puede 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))

salidas:

[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

documentación oficial de la camiseta :

  

En general, si uno iterador utiliza la mayor parte o la totalidad de los datos antes   otro iterador comienza, es más rápido a la lista () en lugar de la T ().

utilizar

Así que lo mejor es utilizar list(iterable) lugar en su caso.

Se puede definir una función que devuelve su generador

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

  return FunctionWithYield

Ahora usted puede hacer tantas veces como se quiera:

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

Usando una función de contenedor para manejar StopIteration

Se puede escribir una función de envoltura simple a su función generadora de generador que realiza un seguimiento cuando el generador está agotado. Lo hará utilizando la excepción StopIteration un generador lanza cuando se llega al final de la iteración.

import types

def generator_wrapper(function=None, **kwargs):
    assert function is not None, "Please supply a function"
    def inner_func(function=function, **kwargs):
        generator = function(**kwargs)
        assert isinstance(generator, types.GeneratorType), "Invalid function"
        try:
            yield next(generator)
        except StopIteration:
            generator = function(**kwargs)
            yield next(generator)
    return inner_func

Como se puede observar anteriormente, cuando nuestra función de contenedor atrapa una excepción StopIteration, simplemente re-inicializa el objeto generador (utilizando otra instancia de la llamada de función).

Y luego, suponiendo que define su función de generador de suministro de algún lugar de la siguiente manera, se puede utilizar la sintaxis de la función decorador de Python para envolver de manera implícita:

@generator_wrapper
def generator_generating_function(**kwargs):
    for item in ["a value", "another value"]
        yield item

No estoy seguro de lo que entiende por preparación caro, pero supongo que en realidad tienen

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

No hay ninguna opción para restablecer los iteradores.Iterador generalmente aparece cuando se itera a través de next() la función.Única manera es tomar una copia de seguridad antes de iterar sobre el objeto iterador.Comprobar a continuación.

La creación de iterador de objetos con elementos de 0 a 9

i=iter(range(10))

La iteración a través de la siguiente función() de la que saldrá

print(next(i))

Convertir el 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 es ya salió.También todos los elementos se rompió cuando convertimos el iterador de la lista.

next(L) 

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

Así que usted necesita para convertir el iterador listas para copia de seguridad antes de iniciar la iteración.La lista podría ser convertido a iterador con iter(<list-object>)

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

Instalar través > 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: crece el consumo de memoria mientras se avanza en el repetidor, así que ten cuidado con las grandes iterables.

Ok, usted dice que quiere llamar a un generador de múltiples veces, pero la inicialización es caro ... ¿Qué pasa algo como esto?

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

Como alternativa, puede simplemente hacer su propia clase que sigue el protocolo de iteración y define una especie de función de 'reset'.

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

Se puede hacer por objeto 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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top