Python实用工具:wrapt 从入门到实战,优雅实现函数包装与装饰器

一、wrapt 库概述

wrapt 是一款专注于 Python 装饰器与函数包装的轻量级库,核心用于稳定、标准地实现函数、类、方法的包装,解决原生装饰器破坏函数元信息、签名异常、嵌套失效等问题。其基于描述符与调用协议工作,保留原对象属性,兼容性强、稳定性高,无额外依赖。该库采用 BSD 许可证,开源免费,适合生产环境使用,缺点是功能聚焦包装,无扩展能力。

二、wrapt 库安装方法

wrapt 支持 Python 3.6+ 所有版本,跨平台兼容,安装方式简洁,使用 pip 即可完成安装,命令如下:

pip install wrapt

安装完成后,可在 Python 交互环境中执行导入命令验证:

import wrapt
print(wrapt.__version__)

若能正常打印版本号,说明安装成功。该库体积极小,安装速度快,不会对项目环境造成额外负担,无论是小型脚本还是大型工程,都可放心引入。

三、wrapt 核心基础使用

3.1 基础装饰器实现

原生 Python 装饰器会导致被装饰函数的 __name____doc__ 等元信息丢失,调用签名异常,而 wrapt 可完美解决该问题。使用 @wrapt.decorator 装饰包装函数,即可创建标准装饰器。

import wrapt

# 定义基础装饰器
def simple_decorator(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        # 执行前逻辑
        print(f"函数 {wrapped.__name__} 即将执行")
        # 调用原函数
        result = wrapped(*args, **kwargs)
        # 执行后逻辑
        print(f"函数 {wrapped.__name__} 执行完成")
        return result
    return wrapper(func)

# 测试函数
@simple_decorator
def test_function(name):
    """这是一个测试函数"""
    print(f"Hello, {name}")

# 调用函数
test_function("wrapt")
# 查看元信息,未被破坏
print("函数名称:", test_function.__name__)
print("函数文档:", test_function.__doc__)

代码说明

  • wrapped 代表被包装的原函数,可直接调用并传递参数;
  • instance 为类方法中的实例,普通函数中为 None
  • argskwargs 分别为位置参数和关键字参数,完整传递给原函数;
  • 装饰后的函数保留原名称、文档字符串,解决原生装饰器痛点。

3.2 无侵入式函数包装

wrapt 支持不使用语法糖,直接对现有函数进行动态包装,适合对第三方库函数、内置函数进行增强,无需修改原函数代码。

import wrapt

# 定义包装逻辑
def log_wrapper(wrapped, instance, args, kwargs):
    print(f"[日志] 调用函数: {wrapped.__name__}")
    return wrapped(*args, **kwargs)

# 原函数
def add(a, b):
    return a + b

# 动态包装函数
wrapped_add = wrapt.wrap_function_wrapper(add, log_wrapper)

# 调用包装后的函数
print(add(1, 2))
print(wrapped_add(3, 4))

代码说明

  • wrapt.wrap_function_wrapper 可直接接收原函数和包装函数,返回新的包装对象;
  • 原函数不受影响,可同时使用原函数和包装后的函数,灵活适配不同场景。

3.3 类与实例方法包装

wrapt 对类方法、静态方法、实例方法的支持十分完善,会自动识别方法类型,正确传递 instance 参数,无需额外处理。

import wrapt

# 定义装饰器
def method_decorator(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        if instance is not None:
            print(f"类 {instance.__class__.__name__} 的方法执行")
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 测试类
class TestClass:
    @method_decorator
    def instance_method(self):
        print("执行实例方法")

    @staticmethod
    @method_decorator
    def static_method():
        print("执行静态方法")

# 调用
obj = TestClass()
obj.instance_method()
TestClass.static_method()

代码说明

  • 实例方法中 instance 为类的实例对象,可通过其访问类属性与实例属性;
  • 静态方法中 instanceNone,装饰器逻辑可自动适配不同类型方法;
  • 无需为不同方法编写不同装饰器,一套逻辑通用。

3.4 带参数的装饰器实现

原生 Python 实现带参数的装饰器需要多层嵌套,代码可读性差,wrapt 可简化带参装饰器的编写,结构清晰易懂。

import wrapt

# 带参数的装饰器
def decorator_with_args(msg):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print(f"自定义信息: {msg}")
        return wrapped(*args, **kwargs)
    return wrapper

# 使用带参装饰器
@decorator_with_args("这是自定义提示信息")
def demo_function():
    print("执行演示函数")

demo_function()

代码说明

  • 外层函数接收装饰器参数,内层通过 @wrapt.decorator 实现包装;
  • 无需多层嵌套函数,代码层级简洁,易于维护和扩展。

四、wrapt 高级功能使用

4.1 函数签名保留

在开发接口、工具库时,函数签名十分重要,原生装饰器会破坏 inspect 模块获取的签名,wrapt 可完整保留。

import wrapt
import inspect

# 装饰器
def signature_decorator(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 带签名的函数
@signature_decorator
def complex_func(a: int, b: str, c: list = None) -> dict:
    return {"a": a, "b": b, "c": c}

# 获取函数签名
print(inspect.signature(complex_func))

代码说明

  • 执行后可正常打印参数类型、默认值、返回值类型;
  • 适合开发 SDK、框架、对外接口,保证调用提示与文档准确性。

4.2 嵌套装饰器兼容

多个装饰器嵌套时,原生装饰器容易出现执行顺序混乱、元信息覆盖问题,wrapt 可保证嵌套稳定执行。

import wrapt

# 第一个装饰器
def decorator1(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print("装饰器1 执行")
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 第二个装饰器
def decorator2(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print("装饰器2 执行")
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 嵌套使用
@decorator1
@decorator2
def nested_func():
    print("原函数执行")

nested_func()

代码说明

  • 执行顺序严格遵循装饰器从上到下、原函数最后执行的规则;
  • 多层嵌套后元信息依然完整,无异常冲突。

4.3 类的整体包装

wrapt 不仅能包装方法,还能对整个类进行包装,批量增强类中所有方法,减少重复代码。

import wrapt

# 类包装器
def class_wrapper(wrapped, instance, args, kwargs):
    print(f"初始化类: {wrapped.__name__}")
    return wrapped(*args, **kwargs)

# 包装类
@wrapt.decorator
def log_class(wrapped, instance, args, kwargs):
    return class_wrapper(wrapped, instance, args, kwargs)

@log_class
class User:
    def __init__(self, name):
        self.name = name

user = User("test")

代码说明

  • 可对类的初始化、实例化过程进行拦截;
  • 适合日志记录、权限校验、参数验证等全局逻辑。

4.4 内置函数与第三方库包装

实际开发中常需增强内置函数或第三方库函数,wrapt 可在不修改源码的前提下完成包装。

import wrapt
import math

# 包装 math.sqrt 函数
original_sqrt = math.sqrt

@wrapt.decorator
def sqrt_wrapper(wrapped, instance, args, kwargs):
    num = args[0]
    print(f"计算平方根: {num}")
    if num < 0:
        raise ValueError("负数不能计算平方根")
    return wrapped(*args, **kwargs)

math.sqrt = sqrt_wrapper(math.sqrt)

# 测试
print(math.sqrt(16))
# print(math.sqrt(-4))  # 会触发异常

代码说明

  • 直接替换模块中的函数,对所有调用生效;
  • 可用于参数校验、异常捕获、性能统计、日志埋点等场景。

五、实际项目综合案例

5.1 接口请求耗时统计装饰器

在 Web 开发、接口调用场景中,经常需要统计函数执行耗时,使用 wrapt 可快速实现通用耗时统计装饰器。

import wrapt
import time

# 耗时统计装饰器
def time_counter(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        start_time = time.time()
        try:
            result = wrapped(*args, **kwargs)
            return result
        finally:
            end_time = time.time()
            cost_time = round((end_time - start_time) * 1000, 2)
            print(f"函数 {wrapped.__name__} 执行耗时: {cost_time} ms")
    return wrapper(func)

# 模拟接口请求
@time_counter
def request_api(url: str, timeout: int = 5) -> dict:
    """模拟接口请求函数"""
    time.sleep(0.5)
    return {"code": 200, "msg": "请求成功", "data": url}

# 调用
request_api("https://example.com/api")
# 查看元信息
print("函数名称:", request_api.__name__)
print("函数文档:", request_api.__doc__)

代码说明

  • 使用 try...finally 确保无论函数是否异常,都能统计耗时;
  • 装饰器可复用于所有需要耗时统计的函数,无侵入、无元信息丢失。

5.2 权限校验装饰器

在后端服务、管理系统中,权限校验是核心功能,使用 wrapt 可编写稳定的权限校验装饰器。

import wrapt

# 模拟用户权限数据
user_info = {
    "username": "admin",
    "is_admin": True
}

# 权限校验装饰器
def admin_required(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        if not user_info.get("is_admin"):
            raise PermissionError("无管理员权限,禁止访问")
        print("权限校验通过")
        return wrapped(*args, **kwargs)
    return wrapper(func)

# 管理员接口
@admin_required
def delete_user(user_id: int):
    print(f"删除用户: {user_id}")

# 调用
delete_user(1001)

代码说明

  • 装饰器独立于业务逻辑,可随时添加或移除;
  • 支持类方法、静态方法、普通函数,兼容性极强。

5.3 函数参数校验装饰器

在数据处理、脚本开发中,参数错误会导致程序崩溃,使用 wrapt 实现统一参数校验装饰器,提升代码健壮性。

import wrapt

# 参数校验装饰器
def param_check(func):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        # 校验位置参数
        for index, arg in enumerate(args):
            if not isinstance(arg, (int, str)):
                raise TypeError(f"第 {index} 个参数类型错误")
        # 校验关键字参数
        for key, value in kwargs.items():
            if value is None:
                raise ValueError(f"参数 {key} 不能为空")
        return wrapped(*args, **kwargs)
    return wrapper(func)

@param_check
def process_data(a, b, c=None):
    print(f"处理数据: {a}, {b}, {c}")

process_data(1, "test", c="data")
# process_data(None, [], c=None)  # 触发参数异常

代码说明

  • 统一封装校验逻辑,避免每个函数重复编写判断代码;
  • 包装后函数签名不变,不影响调用提示。

六、相关资源

  • Pypi地址:https://pypi.org/project/wrapt/
  • Github地址:https://github.com/GrahamDumpleton/wrapt
  • 官方文档地址:https://wrapt.readthedocs.io/

关注我,每天分享一个实用的Python自动化工具。