En Python, ¿hay alguna manera de verificar si una función es una "función generadora" antes de llamarla?
function generator (4)
Digamos que tengo dos funciones:
def foo():
return ''foo''
def bar():
yield ''bar''
El primero es una función normal, y el segundo 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 se verá una implementación directa de is_generator_function()
? Usando 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 declaración de rendimiento, y me pregunto si es posible obtener esa información fácilmente.
¡Gracias!
En realidad, me pregunto cuán útil sería realmente esa función 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é debería is_generator_function()
devolver para baz
y quux
? baz()
devuelve un generador pero no es uno en sí mismo, y es posible que quux()
devuelva un generador o no.
Implementé un decorador que se engancha en la función decorada devuelta / arrojó valor. 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 decorador se llama de forma incondicional: es el valor de retorno que se prueba.
EDITAR: 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
>>> 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 bytecode para la bar
contendrá al menos un YIELD_VALUE
operación YIELD_VALUE
. Recomiendo usar el módulo dis
(redireccionando su salida a una instancia de StringIO y comprobando su valor de getvalue
, por supuesto) porque esto le proporciona una medida de solidez frente a los cambios en los getvalue
de getvalue
; los valores numéricos exactos de los getvalue
de getvalue
cambiarán, pero el valor simbólico desmontado se mantendrá bastante estable ;-).
>>> import inspect
>>>
>>> def foo():
... return ''foo''
...
>>> def bar():
... yield ''bar''
...
>>> print inspect.isgeneratorfunction(foo)
False
>>> print inspect.isgeneratorfunction(bar)
True
- Nuevo en Python versión 2.6