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__ 有利于类型推断和补全支持 |
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': }
错误 | 原因 |
---|---|
忘记加 @wraps |
实际上丢掉了原函数元信息 |
wraps() 用错了对象 |
必须传的是“要包装的原函数” |
把 wraps 当成执行函数 |
它本身是个“返回装饰器的函数” |
多层嵌套装饰器未层层使用 wraps |
每一层都需要加 @wraps(func) |
@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