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.

Dzięki!
Author: Carlos, 2009-12-09

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
 57
Author: Corey Goldberg,
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.

 14
Author: Greg Hewgill,
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;-).

 7
Author: Alex Martelli,
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
 1
Author: Damien,
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