python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python functools.wraps

Python装饰器中常用的functools.wraps的使用

作者:青衫客36

functools.wraps是Python装饰器开发中的关键工具,下面就就来介绍一下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(),但其实 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.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
✅ 反射与 introspectioninspect.getfullargspec(func) 能得到正确参数信息
✅ functools.cache / lru_cache依赖函数的 __name__ 和 __hash__,否则缓存可能出错
✅ unittest mock.patchpatch 也需要定位原函数,名字不能丢
✅ 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)

🧠 八、总结

📌 最佳实践模板

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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文