判断一个函数是否为生成器函数,关键在于函数定义本身是否包含 yield 关键字,而非调用后返回的对象类型。最权威、最可靠的检测方式是使用 inspect.isgeneratorfunction() —— 零副作用、100% 精准,甚至无需执行函数就能完成判断。

要判断一个函数是不是生成器函数,本质上只需要看它定义时是否含有 yield 语句,而不是看它被调用后生成了什么结果。核心理念是:不能真的去执行这个函数。因此,最稳妥、最标准的方法就是使用 inspect.isgeneratorfunction()。下面来分析为什么其他常见做法行不通。
为什么不能用 isinstance(fn(), types.GeneratorType)
这种写法会实际调用函数,带来的后果往往不可控:
- 可能触发副作用——修改全局变量、写入文件、发起网络请求,这些操作可能在检测时意外发生。
- 可能耗尽资源——比如打开连接后未关闭,或生成临时文件句柄。
- 可能直接抛出异常——参数校验失败、IO 错误,导致检测逻辑本身崩溃。
它检查的是“调用结果”而非“函数类型”,属于运行时判断,完全不适合静态类型识别的需求。
为什么不能靠 hasattr 或 __iter__ / __next__
这些属性只能说明对象是迭代器或可迭代对象,但无法区分下面几种情况:
- 一个普通类自己实现了
__iter__和__next__,返回自定义迭代器 - 一个返回
range(10)的普通函数 - 一个真正含有
yield的生成器函数
三者都满足 hasattr(obj, '__iter__') and hasattr(obj, '__next__'),但只有第三种才是生成器函数。因此这种方法基本不可靠。
inspect.isgeneratorfunction() 才是唯一权威方案
该函数直接读取函数对象底层 code object 中的 co_flags 标志位(CO_GENERATOR)。这个标志在函数用 def 定义、编译器编译时就已经确定,无需调用函数,零副作用,100% 精准。具体来说:
- 兼容
@staticmethod、@classmethod、实例方法 - 对普通函数、lambda、内置函数、functools.partial 等返回
False,不会误判 - 标准库自带,无需第三方依赖,Python 3.2+ 全版本通用
实际使用示例
下面这段代码可以用在装饰器、插桩或测试框架中,统一处理生成器函数和普通函数:
import inspect
def safe_wrap(fn):
if inspect.isgeneratorfunction(fn):
def wrapped(*args, **kwargs):
print(f"[GEN] Calling {fn.__name__}")
yield from fn(*args, **kwargs) # 保持生成器协议完整
return wrapped
else:
def wrapped(*args, **kwargs):
print(f"[FUNC] Calling {fn.__name__}")
return fn(*args, **kwargs)
return wrapped
这样包装后的函数,原生行为(比如 for 循环)完全保留,同时又统一了日志或监控逻辑。最关键的是——检测阶段根本不会执行原始函数,安全又可靠。
