¿Cómo puedo ejecutar el código de inicialización para una función de generador inmediatamente, en lugar de en la primera llamada?

Tengo una función de generador que va algo como esto:

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 del bucle while) se ejecute tan pronto como se llame a la función, en lugar de solo cuando se use el generador por primera vez. ¿Cuál es una buena manera de hacer esto?

Quiero hacer esto porque el generador se ejecutará en un subproceso separado (o proceso, o cualquier uso de multiprocesamiento) y no usaré la devolución por un corto tiempo, y la inicialización es algo costosa, así que me gustaría para hacer la inicialización mientras me estoy preparando para usar los valores.

 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 

Necesitaba algo similar. Esto es lo que aterricé. Empuje la función del generador en un interior y devuelva 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() 

Puede crear un iterador “preprimado” con bastante facilidad usando 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 

Supongo que no puede dar ninguno después de que se complete la primera statement, luego en su código de llamada:

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

Para mi caso de uso utilicé una versión modificada de la respuesta de @ncoghlan pero envuelta en una función de fábrica para decorar la función de generación:

 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 

Luego simplemente decora 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 es transparente.