En Python, ¿hay una manera de verificar si una función es una “función generadora” antes de llamarla?

Digamos que tengo dos funciones:

def foo(): return 'foo' def bar(): yield 'bar' 

La primera es una función normal, y la segunda es una función de generador. Ahora quiero escribir algo como esto:

 def run(func): if is_generator_function(func): gen = func() gen.next() #... run the generator ... else: func() 

¿Cómo será una implementación sencilla de is_generator_function() ? Utilizando el paquete de types puedo probar si gen es un generador, pero deseo hacerlo antes de invocar func() .

Ahora considere el siguiente caso:

 def goo(): if False: yield else: return 

Una invocación de goo() devolverá un generador. Supongo que el analizador de python sabe que la función goo() tiene una statement de rendimiento, y me pregunto si es posible obtener esa información fácilmente.

¡Gracias!

 >>> import inspect >>> >>> def foo(): ... return 'foo' ... >>> def bar(): ... yield 'bar' ... >>> print inspect.isgeneratorfunction(foo) False >>> print inspect.isgeneratorfunction(bar) True 
  • Nuevo en la versión 2.6 de Python

En realidad, me pregunto qué tan útil sería realmente tal hipotética is_generator_function() . Considerar:

 def foo(): return 'foo' def bar(): yield 'bar' def baz(): return bar() def quux(b): if b: return foo() else: return bar() 

¿Qué debe is_generator_function() para baz y quux ? baz() devuelve un generador pero no lo es, y quux() puede devolver un generador o no.

 >>> def foo(): ... return 'foo' ... >>> def bar(): ... yield 'bar' ... >>> import dis >>> dis.dis(foo) 2 0 LOAD_CONST 1 ('foo') 3 RETURN_VALUE >>> dis.dis(bar) 2 0 LOAD_CONST 1 ('bar') 3 YIELD_VALUE 4 POP_TOP 5 LOAD_CONST 0 (None) 8 RETURN_VALUE >>> 

Como puede ver, la diferencia clave es que el código de YIELD_VALUE de la bar contendrá al menos un YIELD_VALUE operación YIELD_VALUE . Recomiendo usar el módulo dis (redirigiendo su salida a una instancia de StringIO y verificando su valor de getvalue , por supuesto) porque esto le proporciona una medida de robustez sobre los cambios de getvalue de getvalue : los valores numéricos exactos de los getvalue de getvalue cambiarán, pero el valor simbólico desensamblado se mantendrá bastante estable ;-).

He implementado un decorador que engancha el valor devuelto / cedido de la función decorada. Su básico va:

 import types def output(notifier): def decorator(f): def wrapped(*args, **kwargs): r = f(*args, **kwargs) if type(r) is types.GeneratorType: for item in r: # do something yield item else: # do something return r return decorator 

Funciona porque la función de decoración se llama incondicionalmente: es el valor de retorno que se prueba.


EDIT: Siguiendo el comentario de Robert Lujo, terminé con algo como:

 def middleman(f): def return_result(r): return r def yield_result(r): for i in r: yield i def decorator(*a, **kwa): if inspect.isgeneratorfunction(f): return yield_result(f(*a, **kwa)) else: return return_result(f(*a, **kwa)) return decorator