@wraps(func) 是 Python 标准库 functools 模块提供的一个装饰器,主要用于保留被装饰函数的元信息(metadata)。当我们使用自定义装饰器包装函数时,被装饰后的函数会丢失原函数的名称、文档字符串等信息,而 @wraps(func) 可以解决这个问题。
为什么需要 @wraps(func)?
先看一个不使用 @wraps 的例子,观察装饰器对函数元信息的影响:
def my_decorator(func):def wrapper(*args, **kwargs):"""这是 wrapper 函数的文档字符串"""return func(*args, **kwargs)return wrapper@my_decorator
def original_func():"""这是原函数的文档字符串"""print("原函数执行")# 查看被装饰后的函数信息
print("函数名:", original_func.__name__) # 输出:wrapper(而非 original_func)
print("文档字符串:", original_func.__doc__) # 输出:这是 wrapper 函数的文档字符串(而非原函数的)
可以看到,被装饰后的 original_func 实际上变成了 wrapper 函数,因此它的元信息(__name__、__doc__ 等)也被替换成了 wrapper 的信息。这会导致:
- 调试时难以识别函数身份;
- 生成文档时无法正确提取原函数的说明;
- 依赖函数元信息的代码(如反射)出现异常。
@wraps(func) 的作用
@wraps(func) 的本质是将原函数 func 的元信息“复制”到装饰器内部的 wrapper 函数上,让 wrapper 函数看起来更像原函数。
修改上面的例子,添加 @wraps(func):
from functools import wrapsdef my_decorator(func):@wraps(func) # 保留原函数元信息def wrapper(*args, **kwargs):"""这是 wrapper 函数的文档字符串"""return func(*args, **kwargs)return wrapper@my_decorator
def original_func():"""这是原函数的文档字符串"""print("原函数执行")# 再次查看被装饰后的函数信息
print("函数名:", original_func.__name__) # 输出:original_func(正确保留)
print("文档字符串:", original_func.__doc__) # 输出:这是原函数的文档字符串(正确保留)
此时,original_func 的元信息被正确保留,解决了装饰器导致的元信息丢失问题。
@wraps 复制的元信息包括:
@wraps 会将原函数的以下核心元信息复制到 wrapper 函数:
__name__:函数名称;__doc__:文档字符串(docstring);__module__:函数所在的模块;__annotations__:函数的参数和返回值注解;__defaults__:函数的参数默认值;- 其他与函数身份相关的属性。
实际应用场景
@wraps 在以下场景中尤为重要:
1.** 调试 :确保调试工具(如 pdb)显示的是原函数名,而非 wrapper;
2. 文档生成 :自动文档工具(如 Sphinx)能正确提取原函数的文档字符串;
3. 装饰器嵌套 :多层装饰器时,保留元信息可避免身份混淆;
4. 依赖元信息的框架 **:某些框架(如 Flask、Django)会通过函数名或文档字符串实现特定功能,需确保元信息正确。
总结
@wraps(func) 是装饰器开发中的“最佳实践”,它的作用是保留被装饰函数的元信息,避免因装饰器包装导致函数身份、文档等信息丢失。使用时只需将其放在装饰器内部的 wrapper 函数上方,并传入原函数 func 作为参数即可。
在之前的“递归计时器装饰器”中,@wraps(func) 确保了被装饰的递归函数(如 factorial、fibonacci)在调试或查看元信息时,仍能正确显示其原始名称和文档,而不是内部的 wrapper 函数信息。
