Python装饰器中常用的functools.wraps的使用
作者:青衫客36
functools.wraps
是 Python 装饰器开发中非常关键、但常被忽视的工具。它不仅是代码“优雅性”的体现,还直接影响调试、文档生成、反射等功能的准确性。
一、什么是functools.wraps?
functools.wraps
是一个装饰器,它用于 将被装饰函数的元信息(如名称、文档、注解等)复制到装饰器内部的包装函数上。
为什么需要它?
来看一个没有使用 wraps
的装饰器:
def logger(func): def wrapper(*args, **kwargs): print("调用函数") return func(*args, **kwargs) return wrapper @logger def add(a, b): """计算两个数的和""" return a + b
此时:
print(add.__name__) # ❌ wrapper print(add.__doc__) # ❌ None
我们调用的是 add(),但其实 add 是 wrapper,元信息丢失了。
二、解决方案:使用functools.wraps
from functools import wraps def logger(func): @wraps(func) # 👈 关键:复制原函数的元信息 def wrapper(*args, **kwargs): print("调用函数") return func(*args, **kwargs) return wrapper @logger def add(a, b): """计算两个数的和""" return a + b
现在输出:
print(add.__name__) # ✅ add print(add.__doc__) # ✅ 计算两个数的和
三、底层原理分析:@wraps做了什么?
functools.wraps
是 functools.update_wrapper
的语法糖。
等价于:
@wraps(func) def wrapper(): ... # 等价于 def wrapper(): ... wrapper = functools.update_wrapper(wrapper, func)
update_wrapper(wrapper, func)会做这些事:
属性 | 被复制 |
---|---|
__module__ | 模块名 |
__name__ | 函数名 |
__qualname__ | 完整限定名(含类名) |
__annotations__ | 参数类型注解 |
__doc__ | 文档字符串 |
__dict__ | 自定义属性字典(保持装饰器后可追加属性) |
四、源码分析:wraps的定义
来自 Python 标准库 functools.py
:
def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, # 默认:__module__, __name__, __qualname__, __annotations__, __doc__ updated=WRAPPER_UPDATES): # 默认:__dict__ def decorator(wrapper): return update_wrapper(wrapper, wrapped, assigned, updated) return decorator
所以 @wraps(func) 是返回了一个“装饰器”,给你的 wrapper() 做属性复制。
五、实用场景总结
场景 | 说明 |
---|---|
✅ 调试 | 保留原函数名,调试信息更准确 |
✅ 文档生成 | help(func)、Sphinx 等能读取 docstring |
✅ 反射与 introspection | inspect.getfullargspec(func) 能得到正确参数信息 |
✅ functools.cache / lru_cache | 依赖函数的 __name__ 和 __hash__,否则缓存可能出错 |
✅ unittest mock.patch | patch 也需要定位原函数,名字不能丢 |
✅ IDE提示 / 自动补全 | wrapper.__annotations__ 有利于类型推断和补全支持 |
六、完整示例对比
1. 单层嵌套装饰器
❌ 不使用wraps
def log(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper @log def hello(name: str): """打招呼""" return f"Hi, {name}" print(hello.__name__) # wrapper print(hello.__doc__) # None print(hello.__annotations__) # {}
✅ 使用wraps
from functools import wraps def log(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper
输出:
hello.__name__ # hello
hello.__doc__ # 打招呼
hello.__annotations__ # {'name': <class 'str'>}
2. 多层嵌套装饰器(以2层嵌套为例)
❌ 不使用wraps
def decorator1(func): def wrapper(*args, **kwargs): """wrapper doc 1""" return func(*args, **kwargs) return wrapper def decorator2(func): def wrapper(*args, **kwargs): """wrapper doc 2""" return func(*args, **kwargs) return wrapper @decorator1 @decorator2 def my_function(): """This is the original function""" print("Hello") print(my_function.__name__) # wrapper print(my_function.__doc__) # wrapper doc 1
多个装饰器会逐层覆盖原函数的元信息(metadata),比如 __name__、__doc__。在上述程序中,decorator2 返回一个新的函数 wrapper,decorator1 再包装这个新的函数,得到另一个新的 wrapper,每一层 wrapper 都是新的函数对象,并没有自动保留原函数的 __name__、__doc__、__annotations__ 等属性。
✅ 使用wraps
from functools import wraps def decorator1(func): @wraps(func) def wrapper(*args, **kwargs): """wrapper doc 1""" return func(*args, **kwargs) return wrapper def decorator2(func): @wraps(func) def wrapper(*args, **kwargs): """wrapper doc 2""" return func(*args, **kwargs) return wrapper @decorator1 @decorator2 def my_function(): """This is the original function""" print("Hello") print(my_function.__name__) # my_function print(my_function.__doc__) # This is the original function
对于多层装饰器的情况,建议每一层装饰器都应该加 @wraps(func),否则元信息只保留最外层那一层的,并且在多层装饰器链中,调试会非常混乱
⚠️ 七、常见误区
错误 | 原因 |
---|---|
忘记加 @wraps | 实际上丢掉了原函数元信息 |
wraps() 用错了对象 | 必须传的是“要包装的原函数” |
把 wraps 当成执行函数 | 它本身是个“返回装饰器的函数” |
多层嵌套装饰器未层层使用 wraps | 每一层都需要加 @wraps(func) |
🧠 八、总结
- ✅ 凡是写装饰器 ➜ wrapper 外面加 @wraps(func)
- ✅ 目的是:保留函数元信息,防止“身份丢失”
- ✅ 等价于:update_wrapper(wrapper, func)
- ✅ 一般配合 functools 系列使用,如:@lru_cache、@cache_property
📌 最佳实践模板
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # 执行前逻辑 result = func(*args, **kwargs) # 执行后逻辑 return result return wrapper
到此这篇关于Python装饰器中常用的functools.wraps的使用的文章就介绍到这了,更多相关Python functools.wraps内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!