如何包装函数以保证__code__.co_argcount参数数量准确?
解决高阶函数检查
__code__.co_argcount导致包装函数失效的问题 问题核心
你遇到的问题是:用functools.wraps包装后的函数,因内部使用*args, **kwargs,导致__code__.co_argcount为0,而目标API的高阶函数会直接检查这个只读属性,拒绝不符合参数数量要求的函数,且无法修改API或进行猴子补丁。
解决方案:用可调用类代理属性
我们可以创建一个可调用类实现包装逻辑,通过代理__code__属性到原函数,让高阶函数检查时拿到原函数的参数数量,同时保留包装功能。这种方法适配任意参数数量需求,无需修改目标API。
实现代码
import functools class FunctionWrapper: def __init__(self, func): self.func = func # 复制原函数的元数据(名称、文档、模块等) functools.update_wrapper(self, func) def __call__(self, *args, **kwargs): # 这里添加你的包装逻辑 print("wrapped1!") result = self.func(*args, **kwargs) print("wrapped2!") return result @property def __code__(self): # 代理原函数的__code__属性,让高阶函数拿到原函数的参数数量 return self.func.__code__ # 用这个类作为装饰器 def my_wrapper(func): return FunctionWrapper(func) # 原函数 def original(a, b, c): return None wrapped = my_wrapper(original) # 测试调用 high_order_function(original) # 正常通过 high_order_function(wrapped) # 现在也能正常通过 wrapped(1, 2, 3) # 包装逻辑正常执行
方案有效性说明
- 属性代理:通过
@property装饰的__code__方法,让包装对象的__code__指向原函数的__code__,高阶函数检查co_argcount时会得到原函数的参数数量,符合要求。 - 可调用对象:类实现了
__call__方法,实例可像普通函数一样被调用,内部通过*args, **kwargs兼容任意参数,保证包装逻辑正常执行。 - 元数据保留:
functools.update_wrapper复制原函数的名称、文档字符串、模块等元数据,避免因元数据不一致导致的其他问题。
备选方案:动态生成匹配参数的包装函数
如果不想用类,也可以通过动态代码生成,创建和原函数参数数量完全匹配的包装函数。这种方法无需代理属性,但代码复杂度较高:
import functools def my_wrapper(func): arg_count = func.__code__.co_argcount # 生成参数列表字符串,比如"a,b,c" args_str = ",".join([f"arg{i}" for i in range(arg_count)]) # 动态定义包装函数 exec(f""" @functools.wraps(func) def inner({args_str}, **kwargs): print("wrapped1!") result = func({args_str}, **kwargs) print("wrapped2!") return result """) return locals()["inner"]
该方法需要处理可变参数、关键字参数等复杂场景,通用性和可维护性不如可调用类方案。
内容的提问来源于stack exchange,提问作者DB3D
相关产品推荐
相关产品推荐

