In Python c'è un modo per verificare se una funzione è una “funzione generatore” prima di chiamare?

StackOverflow https://stackoverflow.com/questions/1871685

Domanda

Diciamo che ho due funzioni:

def foo():
  return 'foo'

def bar():
  yield 'bar'

Il primo è una funzione normale, e il secondo è una funzione generatore. Ora voglio scrivere qualcosa del genere:

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

Quale sarà un'implementazione semplice di is_generator_function() assomigliare? Usando il pacchetto types posso testare se gen è un generatore, ma vorrei farlo prima di richiamare func().

Consideriamo ora il caso seguente:

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

Un'invocazione del goo() restituirà un generatore. Presumo che il parser pitone sa che la funzione goo() ha una dichiarazione resa, e mi chiedo se è possibile ottenere che le informazioni facilmente.

Grazie!

È stato utile?

Soluzione

>>> import inspect
>>> 
>>> def foo():
...   return 'foo'
... 
>>> def bar():
...   yield 'bar'
... 
>>> print inspect.isgeneratorfunction(foo)
False
>>> print inspect.isgeneratorfunction(bar)
True
  • Nuovo nella versione Python 2.6

Altri suggerimenti

In realtà, mi chiedo quanto sia utile una tale ipotetica is_generator_function() sarebbe davvero. Prendere in considerazione:

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

Quello che dovrebbe is_generator_function() cambio di baz e quux? baz() restituisce un generatore, ma non è uno per sé, e quux() potrebbe restituire un generatore o non potrebbe.

>>> 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        
>>> 

Come si vede, la differenza fondamentale è che il bytecode per bar conterrà almeno un codice operativo YIELD_VALUE. Mi consiglia di utilizzare il modulo dis (reindirizzare la sua uscita a un'istanza StringIO e controllando la sua getvalue, ovviamente), perché questo vi dà una misura della solidità sulle modifiche bytecode - gli esatti valori numerici dei codici operativi cambieranno, ma il valore simbolico smontato rimarrà abbastanza stabile; -).

Ho implementato un decoratore che si aggancia sulla funzione decorato restituito / prodotto di valore. La sua base 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

Funziona perché la funzione decoratore si unconditionnaly chiamato:. È il valore di ritorno che è testato


Modifica In seguito al commento di Robert Lujo, ho finito con qualcosa di simile:

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top