You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 16:36:00