【问题标题】:Overriding function signature (in help) when using functools.wraps使用 functools.wraps 时覆盖函数签名(在帮助中)
【发布时间】:2021-04-09 20:51:19
【问题描述】:

我正在为带有functools.wraps 的函数创建一个包装器。我的包装器具有覆盖默认参数的效果(并且它不执行任何其他操作):

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

@functools.wraps(add)
def my_add(**kwargs):
    kwargs.setdefault('b', 3)
    return add(**kwargs)

这个my_add 定义的行为与

@functools.wraps(add)
def my_add(*, a=1, b=3):
    return add(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 python-3.x signature functools pydoc


    【解决方案1】:

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

    当您调用help 函数时,这将使用inspect 模块请求有关您的函数的信息。因此,您必须更改函数签名才能更改默认参数。

    现在这不是建议或通常首选的东西,但谁在乎呢?提供的解决方案被认为是 hacky,可能不适用于所有版本的 Python。因此,您可能需要重新考虑 help 函数的重要性......无论如何,让我们先解释一下它是如何完成的,然后是代码和测试用例。


    复制函数

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

    见:

    关于如何做到这一点的想法(我稍后会展示我的版本)。

    复制/更新签名

    下一步是获取函数签名的副本,因为 this 的帖子非常有用。除了我们必须调整签名参数以匹配新的关键字默认参数的部分。

    为此,我们必须更改mappingproxy 的值,我们可以在运行调试器时看到inspect.signature(g) 的返回值。现在到目前为止,这只能通过更改私有变量(带有前导下划线_private 的值)来完成。因此,此解决方案将被视为 hacky,并且不能保证能够承受可能的更新。话虽如此,让我们看看解决方案!


    完整代码

    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
    

    用法

    • f: 是你要更新的函数
    • func_name: 是可选的函数的新名称(如果为空,则保留旧名称)
    • update_kwargs: 是一个字典,包含您要更新的默认参数的键和值。

    注意事项

    • 解决方案是使用copy 变量来制作字典的完整副本,这样就不会影响原来的add 函数。
    • _default 值是私有变量,可以在未来的 python 版本中更改。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-11
      • 2018-06-20
      • 2011-06-30
      • 1970-01-01
      • 2020-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多