2011-04-19 15 views
8

que tienen una función de generador que es algo como esto:¿Cómo puedo ejecutar el código de inicialización para una función de generador inmediatamente, en lugar de ejecutar la primera llamada?

def mygenerator(): 
    next_value = compute_first_value() # Costly operation 
    while next_value != terminating_value: 
     yield next_value 
     next_value = compute_next_value() 

Me gustaría que el paso de inicialización (antes de que el bucle while) para funcionar tan pronto como la función es llamada, en lugar de sólo cuando el generador es primero usado. ¿Cuál es una buena manera de hacer esto?

Quiero hacer esto porque el generador se ejecutará en un subproceso (o proceso, o usos multiprocesamiento por separado) y no utilizaré el retorno durante un tiempo corto, y la inicialización es algo costosa, por lo Me gustaría hacer la inicialización mientras me preparo para usar los valores.

Respuesta

13
class mygenerator(object): 
    def __init__(self): 
     next_value = compute_first_value() 
    def __iter__(self): 
     return self 
    def next(self): 
     if next_value == terminating_value: 
      raise StopIteration() 
     return next_value 
+1

Esta es la manera "correcta". Los otros son hacks. Pueden ser divertidos por sí mismos para crear y usar, más cortos para escribir, pero difíciles de leer y alucinantes para mantener. – jsbueno

6

supongo que se puede producir Ninguno después de que se completó este primer pliego, a continuación, en su Código de llamada:

gen = mygenerator() 
next(gen) # toss the None 
do_something(gen) 
7

Se puede crear un iterador "imprimado" con bastante facilidad mediante el uso de itertools.chain:

from itertools import chain 

def primed(iterable): 
    """Preprimes an iterator so the first value is calculated immediately 
     but not returned until the first iteration 
    """ 
    itr = iter(iterable) 
    try: 
     first = next(itr) # itr.next() in Python 2 
    except StopIteration: 
     return itr 
    return chain([first], itr) 

>>> def g(): 
...  for i in range(5): 
...   print("Next called") 
...   yield i 
... 
>>> x = primed(g()) 
Next called 
>>> for i in x: print(i) 
... 
0 
Next called 
1 
Next called 
2 
Next called 
3 
Next called 
4 
6

necesitaba algo similar. Esto es en lo que aterricé. Empuja la función del generador hacia adentro y devuelve su llamada.

def mygenerator(): 
    next_value = compute_first_value() 

    def generator(): 
     while next_value != terminating_value: 
      yield next_value 
      next_value = compute_next(next_value) 

    return generator() 
0

Por mi caso de uso que utiliza una versión modificada del @ncoghlan answer sino enrollado en una función de fábrica para decorar la función generadora:

import collections, functools, itertools 

def primed_generator(generating_function): 
    @functools.wraps(generating_function) 
    def get_first_right_away_wrapper(*args,**kw): 
     "call the generator function, prime before returning" 
     gen = generating_function(*args,**kw) 
     assert isinstance(gen,collections.Iterator) 
     first_value = next(gen) 
     return itertools.chain((first_value,),gen) 
    return get_first_right_away_wrapper 

A continuación, sólo decorar la función:

@primed_generator 
def mygenerator(): 
    next_value = compute_first_value() # Costly operation 
    while next_value != terminating_value: 
     yield next_value 
     next_value = compute_next_value() 

y el primer valor se calculará de inmediato, y el resultado será transparente.

Cuestiones relacionadas