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

Python 装饰器实用指南:从入门到精通

admin2小时前Python4

装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。这个概念听起来有些抽象,但让我们通过一个具体的例子来理解它的实际价值。

想象一下,你正在开发一个 Web 应用,需要记录每个函数的执行时间。如果没有装饰器,你可能需要在每个函数中手动添加计时代码。这不仅重复,而且违反了 DRY(Don't Repeat Yourself)原则。装饰器正是解决这类问题的优雅方案。

一、装饰器的基础语法

让我们从一个最简单的装饰器开始:

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 的和"""
    total = 0
    for i in range(1, n + 1):
        total += i
    return total

在这个例子中,@timer_decorator 语法糖等价于 calculate_sum = timer_decorator(calculate_sum)。装饰器接收原函数,返回一个包装后的新函数,在调用前后添加了计时逻辑。

二、保留函数元数据

上面的例子有一个小问题:装饰后的函数会丢失原有的名称和文档字符串。Python 的 functools.wraps 可以解决这个问题:

from functools import wraps
import time

def timer_decorator(func):
    @wraps(func)
    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

使用 @wraps(func) 可以确保装饰后的函数保留原有的名称、文档字符串等元数据,这对于调试和代码可读性非常重要。

三、带参数的装饰器

实际开发中,我们经常需要给装饰器传递参数。这需要再增加一层函数嵌套:

def repeat(times):
    """重复执行函数的装饰器,可指定重复次数"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for i in range(times):
                print(f"第 {i + 1} 次执行...")
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

注意装饰器的三层结构:最外层接收装饰器参数,中间层接收被装饰的函数,最内层是实际执行的包装函数。这种模式在编写灵活的装饰器时非常常见。

四、实用装饰器案例

1. 重试装饰器

在网络请求或不稳定操作中,自动重试是非常有用的功能:

def retry(max_attempts=3, delay=1, backoff=2, exceptions=(Exception,)):
    """
    失败自动重试装饰器
    
    参数:
        max_attempts: 最大重试次数
        delay: 初始等待时间(秒)
        backoff: 等待时间倍增系数
        exceptions: 需要捕获的异常类型
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            current_delay = delay
            last_exception = None
            
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    last_exception = e
                    if attempt < max_attempts - 1:
                        print(f"尝试 {attempt + 1} 失败:{e}")
                        print(f"等待 {current_delay} 秒后重试...")
                        time.sleep(current_delay)
                        current_delay *= backoff
                    else:
                        print(f"达到最大重试次数 {max_attempts}")
            
            raise last_exception
        return wrapper
    return decorator

2. 缓存装饰器

对于计算密集型函数,缓存结果可以显著提升性能:

def cache(max_size=128):
    """简单的 LRU 缓存装饰器"""
    def decorator(func):
        cache_dict = {}
        cache_order = []
        
        @wraps(func)
        def wrapper(*args):
            if args in cache_dict:
                print(f"缓存命中:{args}")
                cache_order.remove(args)
                cache_order.append(args)
                return cache_dict[args]
            
            print(f"缓存未命中,计算:{args}")
            result = func(*args)
            
            cache_dict[args] = result
            cache_order.append(args)
            
            if len(cache_dict) > max_size:
                oldest = cache_order.pop(0)
                del cache_dict[oldest]
                print(f"缓存已满,移除最旧项:{oldest}")
            
            return result
        
        wrapper.cache_clear = lambda: cache_dict.clear() or cache_order.clear()
        wrapper.cache_info = lambda: f"缓存大小:{len(cache_dict)}/{max_size}"
        
        return wrapper
    return decorator

3. 权限验证装饰器

在 Web 应用中,权限验证是常见需求:

def require_permission(required_role):
    """权限验证装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if not hasattr(user, 'role'):
                raise PermissionError("用户没有角色属性")
            
            if user.role != required_role:
                raise PermissionError(
                    f"权限不足:需要 {required_role},当前为 {user.role}"
                )
            
            print(f"权限验证通过:{user.name} ({user.role})")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

五、类装饰器

装饰器不仅可以装饰函数,还可以装饰类。类装饰器接收一个类作为参数,返回一个新的类:

def singleton(cls):
    """单例模式装饰器"""
    instances = {}
    
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return get_instance

@singleton
class DatabaseConnection:
    def __init__(self):
        self.connection_count = 0
    
    def connect(self):
        self.connection_count += 1
        return f"连接 #{self.connection_count}"

六、装饰器链

多个装饰器可以叠加使用,执行顺序是从下往上:

def decorator_a(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("[A] 前置处理")
        result = func(*args, **kwargs)
        print("[A] 后置处理")
        return result
    return wrapper

def decorator_b(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("[B] 前置处理")
        result = func(*args, **kwargs)
        print("[B] 后置处理")
        return result
    return wrapper

@decorator_a
@decorator_b
def say_hello():
    print("Hello!")

七、最佳实践与注意事项

1. 始终使用 @wraps:保留原函数的元数据,便于调试和文档生成。

2. 保持装饰器单一职责:每个装饰器只做一件事,这样更容易测试和维护。

3. 注意性能影响:装饰器会增加函数调用开销,在性能敏感的场景中需要权衡。

4. 避免过度嵌套:如果装饰器逻辑过于复杂,考虑将其拆分为多个简单的装饰器。

5. 文档化装饰器:清晰的文档字符串帮助其他开发者理解装饰器的用途和参数。

总结

装饰器是 Python 中强大的工具,它让你能够以优雅的方式扩展函数功能。从简单的计时器到复杂的重试机制,装饰器的应用场景非常广泛。掌握装饰器的关键在于理解其本质——高阶函数,以及熟练运用函数嵌套和闭包的概念。

希望本文能帮助你更好地理解和应用装饰器。在实际项目中,不妨尝试编写自己的装饰器,你会发现代码变得更加简洁和可维护。

相关文章

[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...

[Python 教程] Python 多线程编程指南

Python 多线程编程指南 Python 的 threading 模块提供多线程支持。本文介绍多线程编程的基础和实用技巧。 一、创建线程 import threading import time...

发表评论

访客

看不清,换一张

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