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

Python 装饰器完全指南:从基础到实战应用的深度解析

admin2周前 (03-22)Python21

一、装饰器的基本原理

装饰器的本质是一个接收函数作为参数,并返回一个新函数的高阶函数。Python 使用 @ 语法糖来应用装饰器,这让代码更加简洁易读。

让我们从一个最简单的计时装饰器开始,理解装饰器的工作机制:

import time
from functools import wraps

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

# 使用装饰器
@timer_decorator
def calculate_sum(n):
    """计算 1 到 n 的和"""
    total = 0
    for i in range(1, n + 1):
        total += i
    return total

# 调用函数
result = calculate_sum(1000000)
print(f"计算结果: {result}")

上面的代码展示了装饰器的基本结构。当我们使用 @timer_decorator 装饰 calculate_sum 函数时,实际上等同于:

calculate_sum = timer_decorator(calculate_sum)

关键点说明:

  • @wraps(func) 是一个重要的工具,它保留了被装饰函数的 __name____doc__ 等元信息,避免调试时产生混淆。
  • wrapper 函数接收任意参数 *args, **kwargs,确保装饰器适用于各种签名的函数。
  • wrapper 在调用原函数前后执行额外逻辑,最后返回原函数的执行结果。

二、带参数的装饰器

有时候我们希望装饰器能够接收参数,根据不同的参数表现出不同的行为。这需要创建一个返回装饰器的工厂函数。

下面的例子展示了一个可配置的重试装饰器:

import random

def retry_decorator(max_attempts=3, delay=1.0):
    """
    函数执行失败时自动重试的装饰器
    
    Args:
        max_attempts: 最大重试次数
        delay: 重试之间的延迟时间(秒)
    """
    import time
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            last_exception = None
            
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    last_exception = e
                    if attempts < max_attempts:
                        print(f"第 {attempts} 次尝试失败,{delay} 秒后重试...")
                        time.sleep(delay)
            
            # 所有尝试都失败,抛出最后一次异常
            raise Exception(f"函数执行失败,已尝试 {max_attempts} 次") from last_exception
        
        return wrapper
    return decorator

# 模拟一个可能失败的函数
@retry_decorator(max_attempts=5, delay=0.5)
def unstable_api_call():
    """模拟不稳定的 API 调用"""
    if random.random() > 0.7:  # 30% 成功率
        return "API 调用成功!"
    raise ConnectionError("网络连接失败")

try:
    result = unstable_api_call()
    print(f"最终结果: {result}")
except Exception as e:
    print(f"最终失败: {e}")

带参数装饰器的结构是三层函数:

  1. 外层函数(retry_decorator):接收装饰器参数,返回装饰器函数
  2. 中层函数(decorator):接收被装饰函数,返回 wrapper 函数
  3. 内层函数(wrapper):实际执行逻辑的包装函数

三、类装饰器

装饰器不仅可以应用于函数,还可以应用于类。类装饰器可以动态地修改类的属性和方法,或者创建新的类。

def add_class_methods(cls):
    """为类添加额外方法的装饰器"""
    
    def to_dict(self):
        """将对象转为字典"""
        return {k: v for k, v in self.__dict__.items() if not k.startswith('_')}
    
    def from_dict(cls, data):
        """从字典创建类实例"""
        instance = cls()
        for k, v in data.items():
            setattr(instance, k, v)
        return instance
    
    # 动态添加方法
    cls.to_dict = to_dict
    cls.from_dict = classmethod(from_dict)
    
    return cls

@add_class_methods
class User:
    """用户类"""
    def __init__(self, name=None, email=None):
        self.name = name
        self.email = email

# 使用新增的方法
user = User(name="张三", email="zhangsan@example.com")
print(user.to_dict())

user_data = {"name": "李四", "email": "lisi@example.com"}
user2 = User.from_dict(user_data)
print(user2.to_dict())

类装饰器在框架开发中非常有用,例如 Django 的 @model 装饰器、SQLAlchemy 的 @declarative_base 等。

四、装饰器链

Python 允许我们对一个函数应用多个装饰器,装饰器会按照从下到上的顺序执行。这被称为装饰器链。

def log_args(func):
    """记录函数调用参数的装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[调用日志] {func.__name__}  参数: args={args}, kwargs={kwargs}")
        return func(*args, **kwargs)
    return wrapper

def validate_positive(func):
    """验证参数为正数的装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 检查所有参数
        for arg in args:
            if isinstance(arg, (int, float)) and arg <= 0:
                raise ValueError(f"参数必须为正数,实际值: {arg}")
        for value in kwargs.values():
            if isinstance(value, (int, float)) and value <= 0:
                raise ValueError(f"参数必须为正数,实际值: {value}")
        return func(*args, **kwargs)
    return wrapper

# 使用装饰器链
@log_args
@validate_positive
def calculate_discount(price, discount_rate):
    """计算折扣后价格"""
    return price * (1 - discount_rate)

# 测试
result = calculate_discount(100, 0.2)  # 80.0
print(f"折扣后价格: {result}")

try:
    calculate_discount(-100, 0.2)  # 会抛出异常
except ValueError as e:
    print(f"验证失败: {e}")

装饰器链执行顺序:上面的例子中,当调用 calculate_discount 时,执行顺序是:

  1. log_args 的 wrapper 先执行(最外层)
  2. validate_positive 的 wrapper 次执行(中间层)
  3. 原函数 calculate_discount 最后执行

五、实战案例:缓存装饰器

缓存是装饰器的经典应用场景,可以显著提升重复计算密集型任务的性能。

import hashlib

def memoize(max_size=100):
    """
    缓存函数结果的装饰器
    
    Args:
        max_size: 最大缓存条目数
    """
    def decorator(func):
        cache = {}  # 缓存字典
        
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 生成缓存键
            cache_key = hashlib.md5(
                str((args, tuple(sorted(kwargs.items())))).encode()
            ).hexdigest()
            
            # 检查缓存
            if cache_key in cache:
                print(f"[缓存命中] {func.__name__}")
                return cache[cache_key]
            
            # 计算结果并缓存
            result = func(*args, **kwargs)
            cache[cache_key] = result
            
            # 限制缓存大小(简单的 FIFO 策略)
            if len(cache) > max_size:
                oldest_key = next(iter(cache))
                del cache[oldest_key]
            
            print(f"[缓存存储] {func.__name__}")
            return result
        
        wrapper.cache = cache  # 暴露缓存以便外部操作
        return wrapper
    
    return decorator

# 斐波那契数列计算(经典的重复计算问题)
@memoize(max_size=50)
def fibonacci(n):
    """计算斐波那契数列第 n 项"""
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

# 测试缓存效果
import time

start = time.time()
result1 = fibonacci(35)
end = time.time()
print(f"fibonacci(35) = {result1}, 首次耗时: {end - start:.4f}秒")

start = time.time()
result2 = fibonacci(35)
end = time.time()
print(f"fibonacci(35) = {result2}, 再次耗时: {end - start:.4f}秒 (从缓存读取)")

六、实战案例:权限验证装饰器

在 Web 开发中,装饰器常用于权限验证。下面的例子模拟了一个简单的权限系统:

class PermissionDenied(Exception):
    """权限拒绝异常"""
    pass

def require_permission(*required_roles):
    """
    权限验证装饰器
    
    Args:
        required_roles: 要求的角色列表
    """
    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            # 假设用户角色存储在 self.current_user 中
            user_roles = getattr(self, 'current_user_roles', [])
            
            # 检查是否拥有任一所需角色
            has_permission = any(role in user_roles for role in required_roles)
            
            if not has_permission:
                raise PermissionDenied(
                    f"需要权限: {required_roles}, 当前角色: {user_roles}"
                )
            
            return func(self, *args, **kwargs)
        
        return wrapper
    return decorator

class AdminPanel:
    """管理面板"""
    def __init__(self, user_roles):
        self.current_user_roles = user_roles
    
    @require_permission('admin')
    def delete_user(self, user_id):
        """删除用户(需要管理员权限)"""
        return f"用户 {user_id} 已删除"
    
    @require_permission('admin', 'moderator')
    def ban_user(self, user_id):
        """封禁用户(管理员或版主权限)"""
        return f"用户 {user_id} 已被封禁"

# 测试权限验证
admin_panel = AdminPanel(['admin', 'user'])
print(admin_panel.delete_user(123))  # 成功
print(admin_panel.ban_user(456))     # 成功

moderator_panel = AdminPanel(['moderator'])
print(moderator_panel.ban_user(789))  # 成功

try:
    moderator_panel.delete_user(999)  # 失败,没有 admin 权限
except PermissionDenied as e:
    print(f"权限错误: {e}")

七、最佳实践与注意事项

1. 始终使用 functools.wraps

这保留原函数的元信息,对于调试、文档生成和函数序列化至关重要。

2. 装饰器应该是可组合的

好的装饰器设计应该不影响其他装饰器的正常工作,避免产生意外依赖。

3. 注意装饰器的性能影响

装饰器会引入额外的函数调用开销,在性能敏感的场景中需要谨慎使用。

4. 适当使用类作为装饰器

对于有状态的装饰器(如缓存、计数器),使用类实现可能更清晰:

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

@CountCalls
def process_data(data):
    """处理数据"""
    return data * 2

process_data(10)
process_data(20)
process_data(30)

八、总结

Python 装饰器是一项强大而优雅的工具,它体现了 Python "简洁即美" 的设计哲学。通过装饰器,我们可以:

  • 分离关注点:将核心业务逻辑与横切关注点(日志、缓存、权限等)分离
  • 提升代码复用:一次编写装饰器,多处复用
  • 保持代码简洁:避免重复的样板代码
  • 增强可维护性:修改功能只需调整装饰器,无需修改业务代码

在实际项目中,装饰器广泛应用于日志记录、性能监控、缓存、权限验证、事务管理等场景。掌握装饰器的进阶用法,将让你的 Python 代码更加优雅和高效。

进一步学习:

  • Python 的 functools.lru_cache 提供了内置的缓存装饰器
  • 研究 Flask、Django 等框架中的装饰器实现
  • 探索异步函数装饰器(async/await)的特殊处理

相关文章

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

装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。这个概念听起来有些抽象,但让我们通过一个具体的例子来理解它的实际价值。想象一下,你正在开发一个 Web 应用,需要记录每个函数的执行时间。如果...

Python 装饰器实战:从入门到精通

装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。理解这一点是掌握装饰器的关键。当你看到@decorator 语法时,Python 实际上是在执行 func = decorator(func)...

Python 装饰器进阶:从理解到实战

装饰器是 Python 中一个非常强大的特性,它允许你在不修改原函数代码的情况下,为函数添加额外的功能。很多开发者虽然用过装饰器,但对其底层原理和高级用法理解不深。本文将从基础出发,深入讲解装饰器的工...

Python 装饰器进阶:从入门到实战,写出更灵活的函数增强技巧

# Python 装饰器进阶:从入门到实战,写出更灵活的函数增强技巧 ## 简介 很多 Python 开发者都听过装饰器,也知道怎么写简单的装饰器。但大多数人对装饰器的进阶用法,比如带参数的装饰器、...

Python 异步编程实战:从入门到精通

在 Python 开发中,我们经常会遇到需要同时处理多个 I/O 操作的场景。比如同时向多个 API 发送请求、批量下载文件、或者处理实时数据流。传统的同步方式会阻塞主线程,导致性能瓶颈。而异步编程通...

Python 类型提示实战指南:让代码更健壮

类型提示并不是强制执行的类型系统,而是一种可选的代码文档化工具。它通过注解函数参数和返回值的类型,帮助开发者更清晰地表达意图,同时让 IDE 和类型检查器(如 mypy)能够提前发现潜在的类型错误。...

发表评论

访客

看不清,换一张

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