浅谈Python装饰器中常用的functools.wraps

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(),但其实 addwrapper,元信息丢失了。


✅ 二、解决方案:使用 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.wrapsfunctools.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)

八、总结

  • ✅ 凡是写装饰器 ➜ 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,python,开发语言)