当前位置:首页 > Python > 正文内容

深入理解 Python 装饰器:从基础到高级的完整指南

admin4小时前Python3

什么是装饰器?

装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。装饰器的返回值通常也是一个函数对象。这种设计模式遵循了"开放封闭原则"——对扩展开放,对修改封闭。

基础装饰器的实现原理

在 Python 中,函数是一等对象,这意味着函数可以被赋值给变量、作为参数传递、作为返回值返回。装饰器正是利用了这一特性。让我们从一个简单的示例开始:

def timer_decorator(func):
    """统计函数执行时间的装饰器"""
    import time
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"函数 {func.__name__} 执行耗时: {end_time - start_time:.4f} 秒")
        return result
    return wrapper

@timer_decorator
def calculate_sum(n):
    """计算 1 到 n 的和"""
    return sum(range(n   1))

# 使用
print(calculate_sum(1000000))
# 输出: 函数 calculate_sum 执行耗时: 0.0321 秒
# 输出: 500000500000

上面的代码中,@timer_decorator 语法糖等价于 calculate_sum = timer_decorator(calculate_sum)。wrapper 函数包裹了原始函数,在执行前后添加了计时功能。

带参数的装饰器

有时我们需要创建能够接受参数的装饰器。这需要三层函数结构:最外层接收装饰器参数,中间层接收被装饰函数,最内层是实际的 wrapper 函数。

def repeat(times=3):
    """重复执行了数指定次数的装饰器工厂函数"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(times):
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

@repeat(times=5)
def get_random_number():
    """生成随机数"""
    import random
    return random.randint(1, 100)

# 使用
random_numbers = get_random_number()
print(f"生成的随机数列表: {random_numbers}")

这个示例展示了如何创建一个可以接受参数的装饰器。repeat(times=5) 返回一个真正的装饰器,然后这个装饰器再去包装目标函数。

保留原函数的元信息

使用装饰器后,原函数的元信息(如 __name____doc__ 等)会被替换成 wrapper 函数的元信息。为了解决这个问题,Python 提供了 functools.wraps 装饰器。

from functools import wraps

def logger_decorator(func):
    """带日志记录的装饰器,保留原函数元信息"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[调用] 函数: {func.__name__}")
        print(f"[文档] {func.__doc__}")
        result = func(*args, **kwargs)
        print(f"[返回] 结果: {result}")
        return result
    return wrapper

@logger_decorator
def divide(a, b):
    """除法运算"""
    return a / b

# 验证元信息被保留
print(f"函数名: {divide.__name__}")  # 输出: divide
print(f"函数文档: {divide.__doc__}")  # 输出: 除法运算

使用 @wraps(func) 后,wrapper 函数会继承原函数的名称、文档字符串、参数注解等元信息,这对于调试和文档生成非常重要。

类装饰器

除了函数装饰器,Python 还支持类装饰器。类装饰器需要实现 __call__ 方法,使类的实例可以像函数一样被调用。

class CountCalls:
    """统计函数调用次数的类装饰器"""
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count  = 1
        print(f"函数 {self.func.__name__} 已被调用 {self.count} 次")
        return self.func(*args, **kwargs)

@CountCalls
def process_data(data):
    """处理数据的函数"""
    return [x * 2 for x in data]

# 使用
print(process_data([1, 2, 3]))
print(process_data([4, 5, 6]))
print(process_data([7, 8, 9]))

类装饰器的优势在于可以维护状态。上面的示例中,CountCalls 类实例存储了调用次数,每次调用时自动增加计数器。

装饰器堆叠

多个装饰器可以堆叠使用,执行顺序是从下到上。下面的示例展示了如何组合多个装饰器:

def validate_types(**type_hints):
    """类型验证装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 验证参数类型
            for i, (arg, expected_type) in enumerate(zip(args, type_hints.values())):
                if not isinstance(arg, expected_type):
                    raise TypeError(f"参数 {i} 应该是 {expected_type},实际是 {type(arg)}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

def cache_result(func):
    """缓存结果的装饰器"""
    cache = {}
    @wraps(func)
    def wrapper(*args, **kwargs):
        key = (args, frozenset(kwargs.items()))
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]
    return wrapper

@timer_decorator
@validate_types(a=int, b=int)
@cache_result
def fibonacci(n):
    """计算斐波那契数列"""
    if n <= 1:
        return n
    return fibonacci(n - 1)   fibonacci(n - 2)

# 使用
print(f"fibonacci(35) = {fibonacci(35)}")

在这个例子中,fibonacci 函数被三个装饰器包裹:最底层的 cache_result 提供缓存功能,中间的 validate_types 验证参数类型,最外层的 timer_decorator 统计执行时间。装饰器的执行顺序是从下往上,但实际函数调用时是从外往内。

实际应用场景

装饰器在 Python 开发中有广泛的应用场景:

1. 权限验证

def require_permission(permission):
    """权限验证装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            user_permissions = get_current_user_permissions()
            if permission not in user_permissions:
                raise PermissionError(f"缺少权限: {permission}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

def get_current_user_permissions():
    """模拟获取当前用户权限"""
    return ["read", "write"]

@require_permission("admin")
def delete_user(user_id):
    """删除用户"""
    return f"用户 {user_id} 已删除"

2. 重试机制

def retry(max_attempts=3, delay=1):
    """失败重试装饰器"""
    import time
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f"第 {attempt   1} 次尝试失败,{delay} 秒后重试...")
                    time.sleep(delay)
            return None
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_api_call():
    """模拟不稳定的 API 调用"""
    import random
    if random.random() < 0.7:
        raise ConnectionError("连接失败")
    return "API 调用成功"

3. 性能监控

def performance_monitor(threshold=1.0):
    """性能监控装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            import time
            start_time = time.time()
            result = func(*args, **kwargs)
            execution_time = time.time() - start_time
            
            if execution_time > threshold:
                print(f"⚠️  性能警告: {func.__name__} 执行时间 {execution_time:.2f}s 超过阈值 {threshold}s")
            
            return result
        return wrapper
    return decorator

@performance_monitor(threshold=0.5)
def heavy_computation():
    """耗时计算"""
    import time
    time.sleep(0.8)
    return "计算完成"

装饰器的最佳实践

1. 总是使用 functools.wraps:保留原函数的元信息,便于调试和文档生成。

2. 保持装饰器简洁:装饰器应该专注于单一职责,过于复杂的装饰器会降低代码可读性。

3. 合理使用文档字符串:为装饰器和 wrapper 函数添加清晰的文档说明。

4. 考虑异常处理:装饰器应该妥善处理异常,避免掩盖原始错误信息。

5. 注意装饰器的执行顺序:多个装饰器堆叠时,理解执行顺序至关重要。

总结

Python 装饰器是一个强大而优雅的特性,它让我们能够以声明式的方式为函数添加额外功能,而无需修改原函数代码。从简单的计时功能到复杂的权限验证系统,装饰器都能派上用场。掌握装饰器的高级用法,将让你的 Python 代码更加简洁、可维护和优雅。

希望本文能帮助你深入理解 Python 装饰器,并在实际项目中灵活运用这一强大工具。记住,优秀的代码不仅要有正确的功能,更要具备良好的可读性和可维护性。

相关文章

[Python 教程] OpenCV-Python 入门:图像处理基础详解

OpenCV-Python 入门:图像处理基础详解OpenCV 是一个跨平台计算机视觉库,轻量级且高效,支持 Python 接口。本文将系统介绍 OpenCV 的核心概念和基础操作。一、OpenCV...

[Python 教程] OpenCV 实战:图像与视频文件处理

OpenCV 实战:图像与视频文件处理本文详细介绍如何使用 OpenCV 处理图像和视频文件,包括读取、显示、保存等操作。一、图像文件操作1.1 读取图像import cv2 #&nb...

[Python 教程] OpenCV 绘图教程:图形与文本标注

OpenCV 绘图教程:图形与文本标注本文介绍如何在 OpenCV 中绘制各种图形和添加文本,用于图像标注和可视化。一、绘制基本图形1.1 创建画布import cv2 import&nb...

[Python 教程] NumPy 数组操作详解

NumPy 数组操作详解 NumPy 是 Python 科学计算的基础库,提供高性能的多维数组对象。本文详细介绍 NumPy 数组的核心操作。 一、创建数组 import numpy as np...

[Python 教程] Pandas 数据分析实战

Pandas 数据分析实战 Pandas 是 Python 数据分析的核心库,提供 DataFrame 和 Series 数据结构。本文介绍 Pandas 的实用技巧。 一、创建 DataFrame...

[Python 教程] Matplotlib 数据可视化教程

Matplotlib 数据可视化教程 Matplotlib 是 Python 最常用的绘图库。本文介绍常用图表的绘制方法。 一、基础设置 import matplotlib.pyplot as pl...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。