Functools -- update_wrapper
update_wrapper 是用于“把一个函数的元数据复制到另一个函数”的工具。
就是 wraps() 的底层实现工具。
装饰器写法:
from functools import wraps
其实等价于:
from functools import update_wrapper
wraps 不过是 update_wrapper 的语法糖。
from functools import update_wrapper
def deco(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
update_wrapper(wrapper, func)
return wrapper
@deco
def add(a, b):
"""加法函数"""
return a + b
print(add.__name__) # add
print(add.__doc__) # 加法函数
如果不使用 update_wrapper,结果会是:
wrapper
None
update_wrapper(wrapper,
wrapped,
assigned=WRAPPER_ASSIGNMENTS,
updated=WRAPPER_UPDATES)
含义:
| 参数 | 含义 |
|---|---|
| wrapper | 你的装饰器内部的函数(被修改的对象) |
| wrapped | 原始函数(被装饰的函数) |
| assigned | 需要复制的属性列表(默认有 name, doc…) |
| updated | 需要“更新而不是覆盖”的属性(例如 wrapper.dict) |
默认的:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__',
'__doc__', '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
这意味着:
- 名字、文档、注解、模块都会复制
- wrapper 的
__dict__会更新,而不是覆盖
等价于:
def wraps(wrapped):
return partial(update_wrapper, wrapped=wrapped)
所以这两段代码效果一致:
@wraps(func)
等价于:
update_wrapper(wrapper, func)
假设你写一个装饰器,需要保留原函数自定义属性:
def tag(t):
def decorator(fn):
fn.tag = t
return fn
return decorator
使用 update_wrapper 自动复制:
def deco(fn):
def wrapper(*args, **kwargs):
return fn(*args, **kwargs)
update_wrapper(wrapper, fn)
return wrapper
from functools import update_wrapper
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
update_wrapper(wrapper, func) # 保留元信息
return wrapper
def another_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
update_wrapper(wrapper, func) # 再次保留元信息
return wrapper
@another_decorator
@decorator
def hello():
"""Hello Doc"""
pass
print(hello.__doc__) # Hello Doc
print(hello.__name__) # hello
如果不使用 update_wrapper,函数会被多次 “包装损坏”。
| 问题 | 不用 update_wrapper | 用了 update_wrapper |
|---|---|---|
| 函数名变成 wrapper | ❌ | ✅ |
| 文档丢失 | ❌ | ✅ |
| FastAPI 文档报错 | ❌ | ✅ |
| IDE 参数提示无效 | ❌ | ✅ |
| lru_cache 不能工作 | ❌ | ✅ |
| 调试(traceback)难看懂 | ❌ | ✅ |
| recursive 调用失败 | ❌ | ✅ |
例如你只想复制名字和文档:
update_wrapper(wrapper, func, assigned=('__name__', '__doc__'), updated=())
wrapper.__wrapped__
update_wrapper 会自动加这个属性。
写你自己的装饰器基类:
from functools import update_wrapper
def preserve(fn):
def decorator(wrapper):
return update_wrapper(wrapper, fn)
return decorator
使用:
def debug(fn):
@preserve(fn)
def wrapper(*args, **kwargs):
print(fn.__name__)
return fn(*args, **kwargs)
return wrapper
对你所有装饰器统一保留 metadata。
update_wrapper(wrapper, wrapped):
- 将 wrapped 的元数据复制到 wrapper
- 是 wraps() 的底层
- 保留
__name__,__doc__,__annotations__,__module__等 - 添加
__wrapped__属性 - 对编写装饰器必不可少