
本文介绍在动态方法装饰或调用追踪场景下,如何可靠判断一个可调用对象是否为生成器函数,并提供兼容普通函数与生成器的统一包装方案。
本文介绍在动态方法装饰或调用追踪场景下,如何可靠判断一个可调用对象是否为生成器函数,并提供兼容普通函数与生成器的统一包装方案。
在对对象方法进行运行时插桩(instrumentation)时,一个常见需求是统一拦截所有可调用成员(如实例方法、类方法),并为其添加日志、计时或监控逻辑。然而,若不加区分地对所有 callable 应用同一包装逻辑,会破坏生成器函数的核心行为——因为生成器依赖 yield 语句返回迭代器,而普通函数包装器使用 return 会直接返回值(甚至可能耗尽生成器),导致调用方无法 for 循环或 next() 迭代。
关键在于:不能仅凭返回值判断是否为生成器(例如 fn() 返回 generator 对象,但此时函数已执行,且多次调用将产生新生成器,无法在包装前预判);正确做法是在包装阶段就识别其定义本质——即该函数是否被定义为生成器函数(含 yield 的函数)。Python 标准库 inspect 模块提供了权威、轻量、安全的判定工具:
import inspect
def my_wrapper(fn):
if inspect.isgeneratorfunction(fn):
# 包装生成器函数:保留 yield 行为,仅前置日志
def _w(*args, **kwargs):
print(f"-- called {fn.__qualname__}")
# 逐项 yield 原生成器产出的值,保持迭代协议
for item in fn(*args, **kwargs):
yield item
return _w
else:
# 包装普通可调用对象(函数、方法等)
def _w(*args, **kwargs):
print(f"-- called {fn.__qualname__}")
return fn(*args, **kwargs)
return _w
✅ 为什么用 inspect.isgeneratorfunction?
macOS UI 自动化技能包 —— 用 Python 控制你的 Mac下载让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 它检查函数对象的 co_flags 字节码标志位(CO_GENERATOR),100% 准确反映函数定义意图;
- 不触发函数执行,无副作用;
- 兼容 @staticmethod、@classmethod 及普通实例方法;
- 比 isinstance(fn(), types.GeneratorType) 更早、更安全(后者需调用且可能引发异常)。
⚠️ 注意事项:
- inspect.isgeneratorfunction 仅识别 函数定义,不适用于已调用产生的生成器对象(如 g = gen(); isinstance(g, types.GeneratorType) 才适用);
- 若需支持异步生成器(async def ... yield),应额外使用 inspect.isasyncgenfunction;
- 在 instrument_obj 中,建议将 try/except 细化捕获(如 AttributeError、TypeError),避免静默吞掉关键错误;
- 对 __get__ 描述符(如绑定方法)无需特殊处理——getattr(obj, k) 返回的已是绑定后的可调用对象,inspect.isgeneratorfunction 仍能正确识别其原始函数。
综上,通过 inspect.isgeneratorfunction 实现分支包装,既保持了生成器的迭代语义,又实现了统一的可观测性增强,是生产级方法插桩的推荐实践。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











