2009-12-09 9 views
48

Digamos que tengo dos funciones:En python, ¿hay alguna forma de comprobar si una función es una "función del generador" antes de llamarla?

def foo(): 
    return 'foo' 

def bar(): 
    yield 'bar' 

El primero de ellos 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 aplicación directa de is_generator_function() parece? Usando el paquete types puedo probar si gen es un generador, pero deseo hacerlo antes de invocar func().

Ahora consideremos 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!

+1

Es útil tener en cuenta que si una función contiene una declaración 'yield', a continuación, un' retorno 'declaración dentro de esa función no tiene permiso para tener un argumento. Tiene que ser simplemente 'return' que termina el generador. ¡Buena pregunta! –

+0

Buen punto, 'goo()' no debería ser válido, como sea, al menos aquí (Python 2.6.2). – Carlos

+4

Una nota para los lectores actuales: @GregHewgill comentario anterior ya no es correcto, ahora puede volver con el argumento (que se pasa en el valor atr de StopIteration) – wim

Respuesta

55
>>> import inspect 
>>> 
>>> def foo(): 
... return 'foo' 
... 
>>> def bar(): 
... yield 'bar' 
... 
>>> print inspect.isgeneratorfunction(foo) 
False 
>>> print inspect.isgeneratorfunction(bar) 
True 
  • Nueva versión en Python 2,6
+31

Solo un comentario de 2014 agradeciéndole por proporcionarme una respuesta de 2011 sobre una pregunta de 2009 :) – wim

+2

Pensé que esto resolvió mi problema, pero no funciona a la perfección. Si la función es un generador envuelto, como 'partial (generator_fn, somearg = somevalue)', esto no se detectará. Tampoco se usará una lambda en una circunstancia similar, como 'lambda x: generator_fun (x, somearg = somevalue)'. Estos realmente funcionan como se esperaba; El código estaba experimentando con una función auxiliar que puede encadenar generadores, pero si se encuentra una función normal, la envolverá en un "generador de un solo elemento". –

7
>>> 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 se ve, la diferencia clave es que el código de bytes para bar contendrá al menos un código de operación YIELD_VALUE. Recomiendo usar el módulo dis (redireccionando su salida a una instancia de StringIO y comprobando getvalue, por supuesto) porque esto proporciona una medida de robustez sobre los cambios en los códigos de bytes: los valores numéricos exactos de los códigos de operación cambiarán, pero el valor simbólico desmontado se mantendrá bastante estable ;-).

+0

Alex, ¿cómo te sientes al llamar "blah"? = func() "... luego verifica si type (blah) es un generador? y si no es así, entonces func() ya se llamó :-). Creo que habría sido cómo primero habría investigado cómo hacer esto :-). – Tom

+0

Estaba a punto de escribir lo mismo, pero el Python übergod fue el primero. :-) – paprika

+0

El OP es muy claro en el título de la Q que quiere la información ** antes de llamar ** - mostrando cómo obtenerla ** después de ** llamar no responde a la pregunta dada con las limitaciones claramente expresadas. –

14

En realidad, me pregunto cuán útil sería realmente un hipotético is_generator_function(). Considere:

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() cambio de baz y quux? baz() devuelve un generador pero no es uno en sí mismo, y quux() puede devolver un generador o no.

+1

@Greg, absolutamente - cualquier llamable puede devolver un iterable (que puede ser en particular un iterador y en un generador súper particular, o no), o algo más (o nada en absoluto, al hacer una excepción), dependiendo en argumentos, números aleatorios o la fase de la luna. Una función de generador es invocable de tal manera que "si devuelve algo en vez de aumentar, devolverá un objeto generador" (mientras tanto, no todos los callables que cumplan esas condiciones son funciones gnerator). Los casos de uso son difíciles de inventar. –

+0

Tienes razón, esta es una clase de pregunta hipotética. Llegó mientras estaba leyendo la presentación de David Beazley sobre corutinas: http://www.dabeaz.com/coroutines/ – Carlos

+10

False, True, False, False - respectivamente. No se trata de si la función devuelve una instancia de generador, se trata de si se trata de una función de generador. – wim

1

he implementado un decorador que se engancha en la función decorada regresado valor/cedido. 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 del decorador 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 
+0

Tuve un caso similar y obtuve el error: SyntaxError: 'return' con el argumento dentro del generador. Cuando lo pienso, parece lógico, la misma función no puede ser la función normal y la función del generador al mismo tiempo. ¿Esto realmente funciona en tu caso? –

Cuestiones relacionadas