Python 装饰器高级实战:类装饰器与带参数装饰器完全指南
装饰器本质上是一个接受函数作为参数的函数,它返回一个新的函数来替代原始函数。在基础用法中,装饰器常用于日志记录、性能测试、权限验证等场景。然而,当业务逻辑变得更复杂时,简单的函数装饰器可能无法满足需求,这时就需要使用更高级的装饰器形式。
让我们从一个实际需求开始:假设我们正在构建一个 Web API 框架,需要为多个端点添加统一的访问日志和性能监控功能。我们可以首先实现一个基础装饰器:
```python import time from functools import wraps from datetime import datetime def log_execution(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() print(f"[{datetime.now()}] 开始执行: {func.__name__}") result = func(*args, **kwargs) elapsed = time.time() - start_time print(f"[{datetime.now()}] 完成: {func.__name__}, 耗时: {elapsed:.3f}秒") return result return wrapper ```这个基础装饰器可以很好地工作,但它的灵活性有限。现在让我们进入更高级的主题。
### 类装饰器类装饰器使用类而非函数来实现装饰逻辑。它通过实现 `__call__` 方法,使得类的实例可以像函数一样被调用。类装饰器的优势在于可以维护装饰器自身的状态,实现更复杂的功能。
考虑一个场景:我们需要跟踪每个函数被调用的次数,并且能够在运行时查询这些统计信息。使用类装饰器可以轻松实现:
```python class CallCounter: def __init__(self, func): self.func = func self.call_count = 0 self.__name__ = func.__name__ # 保留原始函数名 def __call__(self, *args, **kwargs): self.call_count += 1 print(f"函数 {self.func.__name__} 第 {self.call_count} 次被调用") return self.func(*args, **kwargs) def get_stats(self): return { 'function_name': self.func.__name__, 'total_calls': self.call_count } ```使用这个类装饰器非常直观:
```python @CallCounter def calculate_square(n): return n * n @CallCounter def calculate_cube(n): return n ** 3 # 测试调用 print(calculate_square(5)) # 函数 calculate_square 第 1 次被调用 print(calculate_cube(3)) # 函数 calculate_cube 第 1 次被调用 print(calculate_square(10)) # 函数 calculate_square 第 2 次被调用 # 查询统计信息 print(calculate_square.get_stats()) # {'function_name': 'calculate_square', 'total_calls': 2} print(calculate_cube.get_stats()) # {'function_name': 'calculate_cube', 'total_calls': 1} ```类装饰器的另一个强大应用场景是实现缓存机制。下面是一个基于 LRU(最近最少使用)策略的缓存装饰器:
```python class LRUCache: def __init__(self, func, max_size=3): self.func = func self.max_size = max_size self.cache = {} # 参数到结果的映射 self.access_order = [] # 记录访问顺序 def __call__(self, *args, **kwargs): # 创建参数的缓存键 cache_key = (args, tuple(sorted(kwargs.items()))) if cache_key in self.cache: # 命中缓存,更新访问顺序 self.access_order.remove(cache_key) self.access_order.append(cache_key) print(f"缓存命中: {args}") return self.cache[cache_key] # 缓存未命中,调用原始函数 result = self.func(*args, **kwargs) # 如果缓存已满,移除最久未使用的项 if len(self.cache) >= self.max_size: oldest_key = self.access_order.pop(0) del self.cache[oldest_key] print(f"缓存淘汰: {oldest_key}") self.cache[cache_key] = result self.access_order.append(cache_key) print(f"缓存存储: {args}") return result def clear_cache(self): self.cache.clear() self.access_order.clear() print("缓存已清空") ```这个缓存装饰器可以显著提高重复计算的性能:
```python @LRUCache(max_size=3) def expensive_computation(x): print(f"执行复杂计算: {x}") return x * x # 第一次调用,执行计算 print(expensive_computation(5)) # 执行复杂计算: 5 # 第二次调用,从缓存获取 print(expensive_computation(5)) # 缓存命中: (5,) # 调用不同的参数 print(expensive_computation(10)) # 执行复杂计算: 10 print(expensive_computation(15)) # 执行复杂计算: 15 # 缓存已满,再次调用新参数会淘汰最老的 print(expensive_computation(20)) # 执行复杂计算: 20, 缓存淘汰 ``` ### 带参数的装饰器带参数的装饰器需要三层嵌套结构:装饰器工厂函数、真正的装饰器函数、以及包装函数。这种结构使得装饰器可以接受自定义参数,提供更高的灵活性。
让我们实现一个重试装饰器,它可以在函数执行失败时自动重试指定次数:
```python def retry(max_attempts=3, delay=1, exceptions=(Exception,)): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_attempts): try: return func(*args, **kwargs) except exceptions as e: if attempt == max_attempts - 1: raise # 最后一次尝试失败,抛出异常 print(f"尝试 {attempt + 1}/{max_attempts} 失败: {e}") print(f"等待 {delay} 秒后重试...") time.sleep(delay) return None return wrapper return decorator ```使用重试装饰器可以让不稳定的网络调用更加健壮:
```python import random @retry(max_attempts=3, delay=1, exceptions=(ValueError, ConnectionError)) def unstable_network_call(url): # 模拟不确定的网络响应 if random.random() < 0.7: raise ConnectionError("网络连接超时") return f"成功获取: {url}" # 测试 try: result = unstable_network_call("https://api.example.com/data") print(f"最终结果: {result}") except Exception as e: print(f"所有尝试失败: {e}") ```带参数装饰器的另一个实用场景是实现权限检查。例如,我们可以创建一个装饰器来验证用户是否具有特定的权限:
```python def require_permission(*required_permissions): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # 假设第一个参数是用户对象 user = args[0] if args else kwargs.get('user') if not user: raise ValueError("用户信息缺失") user_permissions = user.get('permissions', []) missing_permissions = set(required_permissions) - set(user_permissions) if missing_permissions: raise PermissionError( f"权限不足。需要: {', '.join(missing_permissions)}" ) print(f"权限验证通过,执行: {func.__name__}") return func(*args, **kwargs) return wrapper return decorator ```权限装饰器的使用示例:
```python @require_permission('read', 'write') def update_user_data(user, data): print(f"更新用户数据: {data}") return "更新成功" # 测试 admin_user = {'name': 'admin', 'permissions': ['read', 'write', 'delete']} regular_user = {'name': 'user', 'permissions': ['read']} print(update_user_data(admin_user, {'name': '张三', 'age': 25})) # 权限验证通过 # update_user_data(regular_user, {'name': '李四'}) # 抛出 PermissionError ``` ### 组合多个装饰器在 Python 中,我们可以通过叠加多个装饰器来实现功能的组合。装饰器的执行顺序是从内向外,即最下方的装饰器最先执行。
```python @require_permission('read', 'write') @retry(max_attempts=2) @log_execution def critical_operation(user, data): print(f"执行关键操作: {data}") return "操作完成" ```在这个例子中,执行顺序是: 1. `log_execution` 最先执行(记录开始时间) 2. `retry` 在其外层处理重试逻辑 3. `require_permission` 在最外层检查权限
这种组合方式让每个装饰器专注于单一职责,通过组合实现复杂的功能需求。
## 最佳实践建议1. 使用 `functools.wraps` 保留原始函数的元数据,包括函数名、文档字符串等。
2. 保持装饰器的单一职责原则,一个装饰器只完成一个特定功能。
3. 为装饰器添加清晰的文档字符串,说明其用途和参数。
4. 在调试时,可以通过打印或日志记录来追踪装饰器的执行流程。
5. 对于复杂的装饰器逻辑,考虑使用类装饰器以更好地管理状态。
通过掌握类装饰器和带参数装饰器,你将能够编写出更加灵活和强大的 Python 代码,这些技术在实际项目开发中非常实用。
