如何动态修改函数的签名

How to modify the signature of a function dynamically

我正在 Python 中编写一个框架。当用户声明一个函数时,他们会:

def foo(row, fetch=stuff, query=otherStuff)

def bar(row, query=stuff)

def bar2(row)

当后端看到 query= value 时,它​​会根据 value 执行带有查询参数的函数。这样函数就可以访问后端在其范围内完成的某些操作的结果。

目前,我每次都通过检查查询、获取和其他项目是否 None 来构建我的参数,并使用一组与用户要求完全匹配的参数来启动它。否则我得到 "got an unexpected keyword argument" 错误。这是后端的代码:

#fetch and query is something computed by the backend
if fetch= None and query==None:
    userfunction(row)
elif fetch==None:
    userunction (row, query=query)
elif query == None:
    userfunction (row, fetch=fetch)
else:
    userfunction (row,fetch=fetch,query=query)

这不好;对于后端提供的每一个额外的 "service",我需要编写与之前的组合的所有组合。

相反,我想主要采用该函数并手动添加一个命名参数,然后再执行它,删除所有执行这些检查的不必要代码。然后用户就可以使用它真正想要的东西了。

我不希望用户必须通过添加不需要的内容来修改函数(我也不希望他们每次都指定 kwarg)。

所以我想要一个这样的例子,如果这是可行的,一个函数 addNamedVar(name, function) 将变量 name 添加到函数 function.

我想那样做,因为用户函数被调用了很多次,这意味着它会触发我,例如,创建函数的命名 var 的字典(使用检查),然后使用 **dict。我真的很想只修改一次函数以避免任何开销。

这在 AST 中确实可行,这就是我要做的,因为这个解决方案更适合我的用例。但是,您可以通过使用像我展示的代码片段这样的函数克隆方法来更简单地完成我的要求。请注意,此代码 return 具有不同默认值的相同功能。您可以使用此代码作为示例来做任何您想做的事情。 这适用于 python3

def copyTransform(f, name, **args):
    signature=inspect.signature(f)
    params= list(signature.parameters)
    numberOfParam= len(params)
    numberOfDefault= len(f.__defaults__)
    listTuple= list(f.__defaults__)

    for key,val in args.items():
        toChangeIndex = params.index(key, numberOfDefault)
        if toChangeIndex:
            listTuple[toChangeIndex- numberOfDefault]=val

    newTuple= tuple(listTuple)
    oldCode=f.__code__

    newCode= types.CodeType(
        oldCode.co_argcount,             #   integer
        oldCode.co_kwonlyargcount,       #   integer
        oldCode.co_nlocals,              #   integer
        oldCode.co_stacksize,            #   integer
        oldCode.co_flags,                #   integer
        oldCode.co_code,                 #   bytes
        oldCode.co_consts,               #   tuple
        oldCode.co_names,                #   tuple
        oldCode.co_varnames,             #   tuple
        oldCode.co_filename,             #   string
        name,                            #   string
        oldCode.co_firstlineno,          #   integer
        oldCode.co_lnotab,               #   bytes
        oldCode.co_freevars,             #   tuple
        oldCode.co_cellvars              #   tuple
        )

    newFunction=types.FunctionType(newCode, f.__globals__, name, newTuple, f.__closure__)
    newFunction.__qualname__=name #also needed for serialization

如果你想 Pickle 你的克隆函数,你需要用名字做那些奇怪的事情。