Python 使用functools.wrap时重写函数签名(在帮助中)

Python 使用functools.wrap时重写函数签名(在帮助中),python,python-3.x,signature,functools,pydoc,Python,Python 3.x,Signature,Functools,Pydoc,我正在为具有的函数创建包装器。我的包装器具有覆盖默认参数的效果(它不做任何其他事情): def add(*,a=1,b=2): “添加数字” 返回a+b @functools.wrapps(添加) def my_添加(**kwargs): kwargs.setdefault('b',3) 返回添加(**kwargs) 此my\u add定义的行为与 @functools.wrapps(添加) 定义我的添加(*,a=1,b=3): 返回添加(a=a,b=b) 除了我不需要手动输入参数列表 但是

我正在为具有的函数创建包装器。我的包装器具有覆盖默认参数的效果(它不做任何其他事情):

def add(*,a=1,b=2):
“添加数字”
返回a+b
@functools.wrapps(添加)
def my_添加(**kwargs):
kwargs.setdefault('b',3)
返回添加(**kwargs)
my\u add
定义的行为与

@functools.wrapps(添加)
定义我的添加(*,a=1,b=3):
返回添加(a=a,b=b)
除了我不需要手动输入参数列表

但是,当我运行
help(my_add)
时,我看到
add
的帮助字符串,该字符串具有错误的函数名和参数
b
的错误默认参数:

add(*, a=1, b=2)
    Add numbers
如何覆盖此
help()
输出中的函数名和默认参数


(或者,有没有一种不同的方法来定义
my_add
,例如使用一些
magic
函数
my_add=magic(add,func_name='my_add',kwarg_defaults={b':3})
来做我想要做的事情?

让我试着解释一下发生了什么

当您调用
帮助
函数时,将使用模块请求有关您的函数的信息。因此,为了更改默认参数,您必须更改函数签名

现在,这并不是建议的,也不是人们经常喜欢的,但谁会在意这一点呢?所提供的解决方案被认为是有漏洞的,可能不适用于所有版本的Python。因此,您可能需要重新考虑
help
函数的重要性。。。无论如何,让我们先解释一下它是如何完成的,然后是代码和测试用例


复制功能 现在我们要做的第一件事是复制整个函数,这是因为我只想更改新函数的签名,而不是原始函数。这将使新的
my_add
签名(和默认值)与原始的
add
函数分离

见:

对于如何做到这一点的想法(我将在稍后展示我的版本)

复制/更新签名 下一步是获取函数签名的副本,因为这篇文章非常有用。除了我们必须调整签名参数以匹配新关键字默认参数的部分

为此,我们必须更改
mappingproxy
的值,我们可以在
inspect.signature(g)
的返回值上运行调试器时看到该值。到目前为止,这只能通过更改私有变量(带前导下划线的值
\u private
)来实现。因此,此解决方案将被视为黑客攻击,不能保证能够承受可能的更新。也就是说,让我们看看解决方案


完整代码 例子 输出 用法
  • f
    :是要更新的函数
  • func_name
    :可选为函数的新名称(如果为空,则保留旧名称)
  • update\u kwargs
    :包含要更新的默认参数的键和值的字典
笔记
  • 解决方案是使用
    copy
    变量制作字典的完整副本,这样就不会影响原始的
    add
    功能
  • 默认值是一个私有变量,可以在python的未来版本中更改
import inspect
import types
import functools


def update_func(f, func_name='', update_kwargs: dict = None):
    """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)"""
    g = types.FunctionType(
            code=f.__code__,
            globals=f.__globals__.copy(),
            name=f.__name__,
            argdefs=f.__defaults__,
            closure=f.__closure__
    )

    g = functools.update_wrapper(g, f)
    g.__signature__ = inspect.signature(g)
    g.__kwdefaults__ = f.__kwdefaults__.copy()

    # Adjust your arguments
    for key, value in (update_kwargs or {}).items():
        g.__kwdefaults__[key] = value
        g.__signature__.parameters[key]._default = value

    g.__name__ = func_name or g.__name__
    return g


def add(*, a=1, b=2):
    "Add numbers"
    return a + b

my_add = update_func(add, func_name="my_add", update_kwargs=dict(b=3))
if __name__ == '__main__':
    a = 2
    

    print("*" * 50, f"\nMy add\n", )
    help(my_add)

    print("*" * 50, f"\nOriginal add\n", )
    help(add)

    print("*" * 50, f"\nResults:"
                    f"\n\tMy add      : a = {a}, return = {my_add(a=a)}"
                    f"\n\tOriginal add: a = {a}, return = {add(a=a)}")
************************************************** 
My add

Help on function my_add in module __main__:

my_add(*, a=1, b=3)
    Add numbers

************************************************** 
Original add

Help on function add in module __main__:

add(*, a=1, b=2)
    Add numbers

************************************************** 
Results:
    My add      : a = 2, return = 5
    Original add: a = 2, return = 4