使用 functools.wraps 时覆盖函数签名(在帮助中)
Overriding function signature (in help) when using functools.wraps
我正在为 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})
来完成我想要的?)
让我试着解释一下会发生什么。
当您调用 help
函数时,这将使用 inspect 模块请求有关您的函数的信息。因此,您必须更改函数签名,才能更改默认参数。
现在这不是建议的,也不是通常首选的,但是谁在乎呢?提供的解决方案被认为是 hacky,可能不适用于 Python 的所有版本。因此,您可能想重新考虑 help
函数的重要性……无论如何,让我们先解释一下它是如何完成的,然后是代码和测试用例。
复制函数
现在我们要做的第一件事就是复制整个函数,这是因为我只想更改新函数的签名而不是原始函数。这将新的 my_add
签名(和默认值)与原始 add
函数分离。
参见:
- How to create a copy of a python function
- How can I make a deepcopy of a function in Python?
有关如何执行此操作的想法(稍后我会展示我的版本)。
正在复制/更新签名
下一步是获取函数签名的副本,因为 this post 非常有用。除了我们必须调整签名参数以匹配新关键字默认参数的部分。
为此我们必须更改 mappingproxy
的值,当 运行 调试器在 inspect.signature(g)
的 return 值上时我们可以看到。到目前为止,这只能通过更改私有变量(带有前导下划线 _private
的值)来完成。因此,此解决方案将被视为 hacky,并且不能保证能够承受可能的更新。也就是说,让我们看看解决方案!
完整代码
import inspect
import types
import functools
def update_func(f, func_name='', update_kwargs: dict = None):
"""Based on (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 的未来版本中更改。
我正在为 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})
来完成我想要的?)
让我试着解释一下会发生什么。
当您调用 help
函数时,这将使用 inspect 模块请求有关您的函数的信息。因此,您必须更改函数签名,才能更改默认参数。
现在这不是建议的,也不是通常首选的,但是谁在乎呢?提供的解决方案被认为是 hacky,可能不适用于 Python 的所有版本。因此,您可能想重新考虑 help
函数的重要性……无论如何,让我们先解释一下它是如何完成的,然后是代码和测试用例。
复制函数
现在我们要做的第一件事就是复制整个函数,这是因为我只想更改新函数的签名而不是原始函数。这将新的 my_add
签名(和默认值)与原始 add
函数分离。
参见:
- How to create a copy of a python function
- How can I make a deepcopy of a function in Python?
有关如何执行此操作的想法(稍后我会展示我的版本)。
正在复制/更新签名
下一步是获取函数签名的副本,因为 this post 非常有用。除了我们必须调整签名参数以匹配新关键字默认参数的部分。
为此我们必须更改 mappingproxy
的值,当 运行 调试器在 inspect.signature(g)
的 return 值上时我们可以看到。到目前为止,这只能通过更改私有变量(带有前导下划线 _private
的值)来完成。因此,此解决方案将被视为 hacky,并且不能保证能够承受可能的更新。也就是说,让我们看看解决方案!
完整代码
import inspect
import types
import functools
def update_func(f, func_name='', update_kwargs: dict = None):
"""Based on (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 的未来版本中更改。