【问题标题】:In python is there a way to check if a function is a "generator function" before calling it?在 python 中,有没有办法在调用它之前检查一个函数是否是一个“生成器函数”?
【发布时间】:2010-12-24 17:00:01
【问题描述】:

假设我有两个功能:

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语句,我想知道是否可以轻松获取该信息。

谢谢!

【问题讨论】:

  • 请注意,如果函数包含yield 语句,则该函数内部的return 语句不允许有参数。它必须只是终止生成器的return。好问题!
  • 好点,goo() 应该是无效的,但至少在这里(Python 2.6.2)是有效的。
  • 给当前读者的说明:@GregHewgill 上面的评论不再正确,现在您可以返回参数(通过 StopIteration 的值 attr 传递)

标签: python function generator coroutine


【解决方案1】:
>>> import inspect
>>> 
>>> def foo():
...   return 'foo'
... 
>>> def bar():
...   yield 'bar'
... 
>>> print inspect.isgeneratorfunction(foo)
False
>>> print inspect.isgeneratorfunction(bar)
True
  • Python 2.6 版中的新功能

【讨论】:

  • 只是 2014 年的评论,感谢您为 2009 年的问题提供 2011 年的答案 :)
  • 认为这解决了我的问题,但并不完美。如果该函数是一个包装的生成器,例如partial(generator_fn, somearg=somevalue),则不会检测到它。也不会在类似情况下使用 lambda,例如 lambda x: generator_fun(x, somearg=somevalue)。这些实际上按预期工作;他的代码正在试验一个可以链接生成器的辅助函数,但如果找到一个正常的函数,它将把它包装在一个“单项生成器”中。
  • 只是 2020 年的评论,感谢您在 2014 年感谢他为 2009 年的问题提供了 2011 年的答案。
  • 请注意,inspect.isgeneratorfunction() 不适用于 AsyncGenerator。也许时间对那个答案起了作用? ;)
【解决方案2】:
>>> 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() :-)。我认为这就是我首先研究如何做到这一点的方式:-)。
  • 本来打算写同样的,但 Python übergod 排在第一位。 :-)
  • OP 在 Q 的标题中非常清楚地表明他想要调用之前的信息——显示如何获取它之后调用不应答具有明确表达的约束的给定问题。
  • @paprika:哈......我不知道这是否有效......但亚历克斯说它确实......所以+1 :-)......我怀疑其他人会有更好的答案......甚至没有 Guido 本人。
  • @Alex:绝对......我必须说......我没有彻底阅读这个问题:-o。现在我正在看它,这很明显:-)。我有点专注于“运行”功能,并认为可以使用类型轻松完成。
【解决方案3】:

我已经实现了一个装饰器,它挂钩被装饰的函数返回/产生的值。它的基本内容是:

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。仔细想想,这看起来很合乎逻辑,同一个函数不可能同时是普通函数和生成器函数。这对你的情况真的有效吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-15
  • 2021-08-15
  • 2016-08-08
  • 2021-06-29
  • 1970-01-01
  • 2015-10-12
  • 2018-09-28
相关资源
最近更新 更多