
本文介绍在动态方法拦截(如测试用的函数调用追踪)中,如何可靠地区分普通可调用对象与生成器函数,并提供适配两种行为的通用包装方案,避免破坏生成器的迭代语义。
本文介绍在动态方法拦截(如测试用的函数调用追踪)中,如何可靠地区分普通可调用对象与生成器函数,并提供适配两种行为的通用包装方案,避免破坏生成器的迭代语义。
在对对象方法进行运行时插桩(instrumentation)时,一个常见需求是统一记录所有方法调用——例如用于单元测试中的行为审计、性能采样或副作用追踪。然而,若仅用统一的装饰器逻辑处理所有 callable 对象,会意外破坏生成器函数(generator function)的行为:因为生成器函数返回的是 generator 迭代器对象,而普通包装器若采用 return fn(*args, **kwargs),将导致调用方无法使用 for item in obj.gen_method() 语法,从而引发 TypeError: 'generator' object is not callable 或静默失效等难以调试的问题。
关键在于:不能仅靠 isinstance(fn(), types.GeneratorType) 判断——因为这需要实际调用函数,可能触发副作用、耗尽资源,甚至抛出异常;而我们仅需在包装前静态识别“该函数是否为生成器函数”。
Python 标准库 inspect 模块提供了权威、安全且轻量的判定方式:
- ✅ inspect.isgeneratorfunction(obj):判断一个函数对象是否为生成器函数(即定义中包含 yield 的 def 函数);
- ❌ 不要使用 inspect.isgenerator(obj):它检测的是生成器对象实例(如 fn() 的返回值),而非函数本身;
- ❌ 避免 hasattr(obj, '__iter__') and hasattr(obj, '__next__'):这只能判断是否为迭代器,无法区分生成器函数与普通迭代器类实例。
以下是一个生产就绪的包装器实现,支持自动分支处理:
import inspect
def my_wrapper(fn):
if inspect.isgeneratorfunction(fn):
# 分支一:生成器函数 → 包装后仍返回 generator
def _w(*args, **kwargs):
print(f"-- called {fn.__qualname__}")
# 逐项 yield 原生成器产出,保持迭代协议完整
yield from fn(*args, **kwargs)
return _w
else:
# 分支二:普通函数 → 直接调用并返回结果
def _w(*args, **kwargs):
print(f"-- called {fn.__qualname__}")
return fn(*args, **kwargs)
return _w
? 提示:yield from 比 for itm in ...: yield itm 更简洁高效,且能正确传递 .throw() / .close() 等生成器协议方法(Python 3.3+)。
将其集成到你的 instrument_obj 函数中,只需替换原包装逻辑即可:
def instrument_obj(obj):
for k in dir(obj):
try:
method = getattr(obj, k, None)
if not callable(method) or k.startswith("__"):
continue
wrapped = my_wrapper(method) # ✅ 自动适配生成器/普通函数
setattr(obj, k, wrapped)
print(f"-- instrumented {k}")
except Exception as e:
# 建议记录具体异常,便于调试
print(f"-- skip {k}: {type(e).__name__}")
⚠️ 注意事项:
- inspect.isgeneratorfunction 仅对函数对象有效;若遇到 functools.partial、lambda 或绑定方法(bound method),需先用 inspect.unwrap() 或 getattr(fn, '__func__', fn) 提取底层函数;
- 类方法、静态方法、@property 的 getter 若含 yield,同样会被正确识别;
- 此方案不适用于异步生成器(async def + yield),需改用 inspect.isasyncgenfunction 单独处理。
总结:通过 inspect.isgeneratorfunction 进行静态类型识别,配合 yield from 的语义化包装,即可在不侵入业务逻辑的前提下,安全、透明地为所有方法(含生成器)添加可观测性能力——这是构建健壮测试插桩工具链的基础实践之一。










