Есть ли в python способ проверить, является ли функция "функцией-генератором" перед ее вызовом?

Допустим, у меня есть две функции:

def foo():
  return 'foo'

def bar():
  yield 'bar'

Первая - это нормальная функция, а вторая - функция генератора. Теперь я хочу написать что-то вроде этого:

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

Что будет простой реализации is_generator_function() выглядит как? С использованием types пакет я могу проверить, если gen генератор, но я хочу сделать это перед вызовом func(),

Теперь рассмотрим следующий случай:

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

Вызов goo() вернет генератор. Я предполагаю, что парсер Python знает, что goo() Функция имеет выражение выхода, и мне интересно, можно ли получить эту информацию легко.

Спасибо!

4 ответа

Решение
>>> import inspect
>>> 
>>> def foo():
...   return 'foo'
... 
>>> def bar():
...   yield 'bar'
... 
>>> print inspect.isgeneratorfunction(foo)
False
>>> print inspect.isgeneratorfunction(bar)
True
  • Новое в Python версии 2.6

На самом деле, мне интересно, насколько полезна такая гипотетическая is_generator_function() было бы действительно. Рассматривать:

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

Что должно is_generator_function() вернуться за baz а также quux? baz() возвращает генератор, но не сам по себе, и quux() может вернуть генератор или нет.

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

Как видите, ключевое отличие состоит в том, что байт-код для bar будет содержать по крайней мере один YIELD_VALUE опкод. Я рекомендую использовать dis модуль (перенаправить его вывод в экземпляр StringIO и проверить его getvalue, конечно), поскольку это дает вам меру надежности по сравнению с изменениями байт-кода - точные числовые значения кодов операций изменятся, но разобранное символьное значение останется довольно стабильным;-).

Я реализовал декоратор, который подключает декорированную функцию к возвращенному / полученному значению. Его основной идет:

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

Это работает, потому что функция декоратора вызывается безоговорочно: проверяется возвращаемое значение.


РЕДАКТИРОВАТЬ: После комментария Роберта Лужо, я закончил что-то вроде:

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
Другие вопросы по тегам