Czy w Pythonie istnieje sposób na sprawdzenie, czy funkcja jest "funkcją generatora" przed wywołaniem jej?
Powiedzmy, że mam dwie funkcje:
def foo():
return 'foo'
def bar():
yield 'bar'
Pierwsza jest funkcją normalną, a druga jest funkcją generatora. Teraz chcę napisać coś takiego:
def run(func):
if is_generator_function(func):
gen = func()
gen.next()
#... run the generator ...
else:
func()
Jak będzie wyglądała prosta implementacja is_generator_function()
? Korzystając z pakietu types
mogę sprawdzić, czy gen
jest generatorem, ale chcę to zrobić przed wywołaniem func()
.
Rozważ teraz następujący przypadek:
def goo():
if False:
yield
else:
return
Wywołanie goo()
zwróci generator. Przypuszczam, że python parser wie, że funkcja goo()
ma instrukcję yield I zastanawiam się, czy można łatwo uzyskać te informacje.
4 answers
>>> import inspect
>>>
>>> def foo():
... return 'foo'
...
>>> def bar():
... yield 'bar'
...
>>> print inspect.isgeneratorfunction(foo)
False
>>> print inspect.isgeneratorfunction(bar)
True
- Nowość w Pythonie w wersji 2.6
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-20 20:09:14
Właściwie, to zastanawiam się, jak przydatna byłaby taka hipotetyczna hipoteza. Consider:
def foo():
return 'foo'
def bar():
yield 'bar'
def baz():
return bar()
def quux(b):
if b:
return foo()
else:
return bar()
Co powinno is_generator_function()
zwrócić dla baz
i quux
? baz()
zwraca generator, ale sam nim nie jest, i quux()
może zwrócić generator lub nie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-09 05:24:28
>>> 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
>>>
Jak widzisz, zasadnicza różnica polega na tym, że kod bajtowy dla bar
będzie zawierał co najmniej jeden kod opcode YIELD_VALUE
. Zalecam użycie modułu dis
(przekierowanie jego wyjścia do instancji StringIO i sprawdzenie jego getvalue
, oczywiście), ponieważ zapewnia to miarę solidności zmian kodu bajtowego - dokładne wartości liczbowe opcodów ulegną zmianie, ale zdemontowana wartość symboliczna pozostanie dość stabilna;-).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-09 05:10:44
Zaimplementowałem dekorator, który zaczepia się na dekorowanej funkcji zwracającej / przekazującej wartość. Jego podstawowe to:
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
Działa, ponieważ funkcja dekoratora jest bezwarunkowo wywoływana: jest to wartość zwracana, która jest testowana.
EDIT: po komentarzu Roberta Lujo, skończyło się na czymś takim:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-16 13:59:15