Python如何在不改变函数参数签名的前提下给现有函数注入代码
方案1:包装函数完全克隆原函数参数签名
这个方案不需要修改原函数的代码逻辑,只需要让包装函数绕过inspect的签名检查即可,有两种实现方式:
标准库原生实现
inspect.signature读取函数签名时会优先读取函数的__signature__属性,我们可以手动将原函数的签名赋值给包装函数,再配合functools.wraps复制原函数的元信息,即可通过检查:
import inspect import functools def auto_respond(): def wrapper(callback): # 提前获取原函数的签名 orig_sig = inspect.signature(callback) # 复制原函数元信息 @functools.wraps(callback) async def auto_callback(*args, **kwargs): # 绑定参数获取ctx,兼容位置传参和关键字传参 bound_args = orig_sig.bind(*args, **kwargs) bound_args.apply_defaults() ctx = bound_args.arguments['ctx'] # 先执行respond await ctx.respond() # 调用原函数 return await callback(*args, **kwargs) # 给包装函数挂载原函数的签名,骗过inspect检查 auto_callback.__signature__ = orig_sig return auto_callback return wrapper
第三方库简化实现
如果允许引入第三方依赖,可以用makefun库直接生成和原函数签名完全一致的包装函数,不需要手动处理签名:
from makefun import wraps import inspect def auto_respond(): def wrapper(callback): @wraps(callback) async def auto_callback(*args, **kwargs): # 同样用签名绑定获取ctx bound_args = inspect.signature(callback).bind(*args, **kwargs) bound_args.apply_defaults() await bound_args.arguments['ctx'].respond() return await callback(*args, **kwargs) return auto_callback return wrapper
方案2:直接注入代码到原函数开头
这个方案通过修改原函数的AST(抽象语法树),在函数体最前面插入await ctx.respond()的调用,完全保留原函数的签名、注解、默认值,不需要额外包装层:
import ast import inspect import functools def auto_respond(): def wrapper(callback): # 获取原函数源代码并解析为AST src = inspect.getsource(callback) tree = ast.parse(src) func_node = tree.body[0] # 构造await ctx.respond()的AST节点 respond_stmt = ast.Expr( value=ast.Await( value=ast.Call( func=ast.Attribute( value=ast.Name(id='ctx', ctx=ast.Load()), attr='respond', ctx=ast.Load() ), args=[], keywords=[] ) ) ) # 插入到函数体最前面 func_node.body.insert(0, respond_stmt) # 修复AST的位置信息,避免编译报错 ast.fix_missing_locations(tree) # 编译为字节码 compiled = compile(tree, filename="<ast>", mode="exec") # 在原函数的命名空间中执行,拿到修改后的函数 new_func_globals = callback.__globals__.copy() exec(compiled, new_func_globals) new_func = new_func_globals[callback.__name__] # 复制原函数的元信息 functools.update_wrapper(new_func, callback) return new_func return wrapper
注意:这个方案要求原函数能被
inspect.getsource读取到源代码,嵌套函数、动态生成的函数、C扩展实现的函数无法使用该方案。
内容的提问来源于stack exchange,提问作者404kuso
相关产品推荐
相关产品推荐

