如何将一行代码注入到现有函数中?

How to inject a line of code into an existing function?

我的函数如下所示:

async def callback(ctx, first_arg, second_arg=None)
    ...

其中参数名称、计数和所有内容始终可以不同。 在第一个参数中是一个名为 respond() 的方法,我想制作一个装饰器 returns 修改后的回调,其中 ctx.respond() 被调用。

我想备份原始回调,然后创建一个“假”回调,其中调用 ctx.respond() 方法,然后调用真正的回调,但它不会工作,因为在我的装饰器之后是另一个装饰器,它将检查函数参数

装饰者

def auto_respond():
    def wraper(callback):
        # this is what I thought of
        b_callback = callback
        async def auto_callback(ctx, here comes the problem):
            await ctx.respond()
            return await b_callback(ctx, the problem is here too)
        return auto_callback
    return wraper

问题是,我无法设置函数的正确参数,因为我不知道它们将如何设置。

本来我想可能用*args**kwargs来接收params直接传过去,结果检查不行


假设我有这个例子

@the_other_decorator()
@auto_respond()
async def callback(ctx, user=None):
    ...

而在另一个装饰器中,将使用 inspect.signature(callback).parameters

检查参数

the_other_decorator

def the_other_decorator():
    def wraper(callback):
        params = inspect.signature(callback).parameters
        for name in params:
            param = params.get(name)
            if param.annotation != param.empty:
                ...
            elif param.default != inspect._empty:
                ...
        ...
    return wraper

所以我的下一个解决方案是,如果我能以某种方式“注入”一行代码到回调函数,获取第一个参数并使用它的 respond 方法,我将“绕过”参数检查

请注意,需要进行参数检查,因为我需要在装饰器中获取有关它们的一些信息

所以现在有两个选择,第一个是像我说的那样注入一行代码以某种方式“克隆”回调函数的参数并设置它们到“假”回调函数

顺便说一句,抱歉英语不好,如果我没有表达正确或者在这个问题中缺少某些信息,请告诉我,以便我改进问题!

我想你想要这样的东西(为了简单起见,我删除了异步的东西,你可以随意添加回来):

import functools
import inspect
def auto_respond(func):
    # This will preserve the signature of func
    @functools.wraps(func)
    def wraper(*args, **kwargs):
        ctx = args[0]
        ctx.respond()
        return func(*args, **kwargs)
    return wraper

class Context:
    def respond(self):
        print("Response")

my_context = Context()  
@auto_respond
def my_func_1(ctx, arg1, arg2):
    print(arg1, arg2)
my_func_1(my_context, "a", "b")
print(inspect.signature(my_func_1).parameters)

@auto_respond
def my_func_2(ctx, arg1, arg2, arg3, arg4=None):
    print(arg1, arg2, arg3, arg4)
my_func_2(my_context, "a", "b", "c", "d")
print(inspect.signature(my_func_2).parameters)

>>>>>>
Response
a b
OrderedDict([('ctx', <Parameter "ctx">), ('arg1', <Parameter "arg1">), ('arg2', <Parameter "arg2">)])
Response
a b c d
OrderedDict([('ctx', <Parameter "ctx">), ('arg1', <Parameter "arg1">), ('arg2', <Parameter "arg2">), ('arg3', <Parameter "arg3">), ('arg4', <Parameter "arg4=None">)])

如果你想获取函数并将其存储在某个地方,你也可以这样做:

def my_func_3(ctx, arg1, arg2):
    print(arg1, arg2)
my_auto_respond_func = auto_respond(my_func_3)

my_auto_respond_func(my_context, "a", "b")

要回答问题的第二部分,您可以使用与 auto_respond 相同的结构在此装饰器之上链接另一个装饰器,并且您将能够在传递这些参数时检查参数通过 *args**kwargs 就像第一个装饰器不存在一样感谢 @functools.wraps