装饰器@wraps(func)详解
1. @wraps(func)
的核心作用
@wraps
是 Python 标准库 functools
提供的装饰器,用于保留被装饰函数的原始元信息。
它通过将原函数的 __name__
、__doc__
、__module__
等属性复制到装饰器内部的包装函数中,避免装饰器对函数身份信息的“掩盖”。
2. 元信息丢失的问题(无 @wraps
时)
示例代码
def simple_decorator(func):def wrapper(*args, **kwargs):"""Wrapper docstring"""return func(*args, **kwargs)return wrapper@simple_decorator
def say_hello():"""Original docstring"""print("Hello!")print(say_hello.__name__) # 输出什么?
print(say_hello.__doc__) # 输出什么?
输出结果
wrapper # 函数名变为装饰器内部的 wrapper
Wrapper docstring # 文档字符串被覆盖
问题分析
- 被装饰后,函数的
__name__
和__doc__
变成了装饰器内部wrapper
的信息。 - 这会导致调试困难(日志中显示
wrapper
而非原函数名)、文档工具(如 Sphinx)无法正确生成文档。
3. 使用 @wraps(func)
修复元信息
改进代码
from functools import wrapsdef better_decorator(func):@wraps(func) # 关键点:复制元信息def wrapper(*args, **kwargs):"""Wrapper docstring"""return func(*args, **kwargs)return wrapper@better_decorator
def say_hello():"""Original docstring"""print("Hello!")print(say_hello.__name__) # 输出 'say_hello'
print(say_hello.__doc__) # 输出 'Original docstring'
输出结果
say_hello # 函数名保留原名称
Original docstring # 文档字符串未被覆盖
关键变化
@wraps(func)
将原函数say_hello
的元信息复制到wrapper
函数。- 调试和文档工具看到的是原函数的元数据,而非装饰器内部的实现细节。
4. @wraps
保留的具体属性
@wraps
默认会复制以下属性(完整列表见 functools.wraps
文档):
属性名 | 作用 |
---|---|
__name__ | 函数名 |
__doc__ | 文档字符串 |
__module__ | 所在模块名 |
__annotations__ | 类型注解 |
__dict__ | 其他自定义属性 |
5. 在类方法装饰器中的必要性
原问题代码分析
class RouterClient:@keep_alivedef execute(self, command):"""Send a command to the device"""return self.conn.send_command(command)
- 无
@wraps
时:execute.__name__
会变成"wrapper"
。help(RouterClient.execute)
显示装饰器的文档而非原方法文档。
- 有
@wraps
时:- 保留原始方法名和文档,便于其他开发者理解代码。
6. 验证 @wraps
效果的实用技巧
检查函数身份
def check_metadata(func):print(f"Function name: {func.__name__}")print(f"Docstring: {func.__doc__}")print(f"Is it a wrapper? {'wrapper' in func.__name__}")check_metadata(say_hello) # 装饰后仍显示原函数信息
输出示例(使用 @wraps
时)
Function name: say_hello
Docstring: Original docstring
Is it a wrapper? False
7. 为什么装饰器会覆盖元信息?
- Python 装饰器的本质:
@decorator
等价于func = decorator(func)
。 - 装饰器返回的新函数(如
wrapper
)会替换原函数,而函数的元信息绑定在函数对象本身。 @wraps
通过functools.update_wrapper
实现属性复制,解决身份丢失问题。
8. 总结
- 何时用
@wraps
:任何时候编写装饰器时,除非故意想隐藏原函数信息。 - 核心价值:
- 调试时显示有意义的函数名(而非
wrapper
)。 - 保留文档字符串和类型注解,提升代码可维护性。
- 兼容依赖元信息的工具(如测试框架、文档生成器)。
- 调试时显示有意义的函数名(而非