In Python c'è un modo per verificare se una funzione è una “funzione generatore” prima di chiamare?
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!
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