在Python中,在调用函数之前有没有一种方法可以检查该函数是否为“生成器函数”?

74

假设我有两个函数:

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()函数有一个yield语句,并且我想知道是否可以轻松地获取该信息。

谢谢!


3
需要注意的是,如果一个函数包含了yield语句,那么该函数内的return语句不允许有参数。它必须是仅含return的形式,用于终止生成器。很好的问题! - Greg Hewgill
好的观点,“goo()”不应该是有效的,但是它确实是有效的,至少在这里(Python 2.6.2)。 - Carlos
7
给当前的读者一条说明:@GregHewgill在上面的评论已经不再正确,现在您可以使用参数(这个参数通过StopIteration的value属性传递)返回结果。 - wim
3个回答

88
>>> import inspect
>>> 
>>> def foo():
...   return 'foo'
... 
>>> def bar():
...   yield 'bar'
... 
>>> print inspect.isgeneratorfunction(foo)
False
>>> print inspect.isgeneratorfunction(bar)
True
  • Python 2.6 版本的新特性

51
感谢您在2011年回答了一个2009年的问题,这是一条2014年的评论,只是想表达感激之情 :) - wim
4
我以为这解决了我的问题,但并不完美。如果函数是包装过的生成器,例如partial(generator_fn, somearg=somevalue),那么这将无法被检测到。在类似情况下使用lambda表达式也会有同样的问题,比如 lambda x: generator_fun(x, somearg=somevalue)。这些实际上按预期工作;代码正在尝试使用一个辅助函数链接生成器,但如果找到常规函数,它就会将其包装成“单项生成器”。 - Chris Cogdon
6
只是一个2020年的评论,感谢你在2014年感谢他提供了一个关于2009年问题的2011年答案。 - Vitalate
请注意,inspect.isgeneratorfunction() 对于AsyncGenerator无效。也许时间对那个答案起了作用?;) - Cyril N.
@CyrilN。针对此问题,可以使用inspect.isasyncgenfunction()函数进行检查。文档 - lovre

7
>>> 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),因为这可以为您提供对字节码更加健壮的控制——操作码的确切数字值会发生变化,但反汇编的符号值会保持相对稳定;-)。


Alex,你觉得调用“blah = func()”然后检查type(blah)是否为生成器怎么样?如果不是,那么func()已经被调用了:-)。我认为这可能是我最初研究如何做到这一点的方式:-)。 - Tom
本来我也要写同样的东西,但 Python 大神比我先发了。 :-) - paprika
1
OP在问题标题中非常清楚地表明他想要在调用之前获取信息--展示如何在调用之后获取信息并不能回答具有明确表达的限制条件的给定问题。 - Alex Martelli
@paprika:哈...我不知道这是否有效...但是Alex说它有效...所以+1 :-)...我怀疑没有人会有更好的答案...甚至包括Guido本人。 - Tom
@Alex:完全正确...我必须说...我没有仔细阅读问题:-o。现在我正在看它,很明显:-)。我有点专注于“run”函数,并认为可以轻松地用类型完成。 - Tom
@paprika,谢谢你的赞誉,但是那个头衔应该属于Guido van Rossum(据我所知他在SO上不活跃)。在Python神殿中,我最多只能成为一个英灵(性别问题使我无法成为女武神;-)。 - Alex Martelli

1
我已经实现了一个装饰器,它钩取被装饰函数的返回或生成值。其基本原理如下:

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

它能够工作是因为装饰器函数无条件地被调用:测试的是返回值。

编辑:根据Robert Lujo的评论,我最终得到了如下内容:

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

我曾经遇到过类似的情况,出现了错误:SyntaxError: 'return' with argument inside generator。仔细想想,这是很合理的,同一个函数不能同时作为普通函数和生成器函数。你的情况真的可以这样做吗? - Robert Lujo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接