Python 装饰器进阶应用与最佳实践
装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。这种设计模式在 Python 中被广泛应用,从日志记录、性能分析到权限验证,装饰器都能发挥重要作用。理解装饰器的工作原理是掌握进阶应用的关键。
让我们从一个带参数的装饰器开始。在实际开发中,我们经常需要根据不同的条件来控制装饰器的行为。例如,创建一个重试装饰器,可以指定重试次数和延迟时间:
```python import time from functools import wraps def retry(max_attempts=3, delay=1): 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: last_exception = e attempts += 1 if attempts < max_attempts: time.sleep(delay) print(f"重试 {attempts}/{max_attempts - 1}...") raise last_exception return wrapper return decorator # 使用示例 @retry(max_attempts=3, delay=1) def unstable_api_call(): import random if random.random() < 0.7: raise ValueError("API 请求失败") return "成功" ```这个装饰器展示了装饰器的核心模式:外层函数接收参数,内层函数接收被装饰的函数,最内层函数才是真正的包装器。使用 functools.wraps 保留原函数的元信息是一个重要的最佳实践。
类装饰器提供了另一种实现方式,特别适合需要维护状态的场景。下面是一个缓存装饰器的类实现:
```python from functools import wraps class cached: def __init__(self, ttl=60): self.ttl = ttl self.cache = {} def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): import time key = str(args) + str(kwargs) current_time = time.time() if key in self.cache: value, timestamp = self.cache[key] if current_time - timestamp < self.ttl: return value result = func(*args, **kwargs) self.cache[key] = (result, current_time) return result return wrapper @cached(ttl=10) def expensive_computation(n): print("执行昂贵计算...") return sum(i * i for i in range(n)) ```类装饰器的优势在于可以方便地管理缓存状态,并且支持在运行时动态修改配置。这种模式在需要复杂状态管理的场景下非常有用。
装饰器链是 Python 装饰器的一个重要特性。多个装饰器可以叠加使用,它们会按照从下到上的顺序依次执行。这种组合方式让我们可以像搭积木一样构建复杂的功能:
```python def log_calls(func): @wraps(func) def wrapper(*args, **kwargs): print(f"调用 {func.__name__} 参数: {args}, {kwargs}") result = func(*args, **[kwargs]) print(f"{func.__name__} 返回: {result}") return result return wrapper def validate_types(*type_args): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for i, (arg, expected_type) in enumerate(zip(args, type_args)): if not isinstance(arg, expected_type): raise TypeError( f"参数 {i} 应为 {expected_type.__name__}, " f"实际为 {type(arg).__name__}" ) return func(*args, **kwargs) return wrapper return decorator @log_calls @validate_types(int, int) @retry(max_attempts=2) def divide(a, b): return a / b # 调用时会依次经过 retry -> validate_types -> log_calls result = divide(10, 2) ```装饰器链的执行顺序需要注意理解:当使用多个装饰器时,它们会从下往上依次包装函数。这意味着最上面的装饰器最先执行,最下面的装饰器最后执行。理解这个顺序对于调试和维护装饰器链至关重要。
在实际项目中,装饰器常用于权限验证、日志记录、性能监控等场景。下面是一个权限验证装饰器的实现:
```python class PermissionDenied(Exception): pass 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 PermissionDenied( 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): print(f"删除用户: {user_id}") return True ```性能监控装饰器可以帮助我们快速识别性能瓶颈。下面是一个简单但实用的性能分析装饰器:
```python import time import statistics from collections import defaultdict class performance_monitor: def __init__(self): self.call_stats = defaultdict(list) def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.perf_counter() result = func(*args, **kwargs) end_time = time.perf_counter() duration = (end_time - start_time) * 1000 # 转换为毫秒 self.call_stats[func.__name__].append(duration) return result return wrapper def get_stats(self, func_name): if func_name not in self.call_stats: return None durations = self.call_stats[func_name] return { "count": len(durations), "avg": statistics.mean(durations), "min": min(durations), "max": max(durations) } monitor = performance_monitor() @monitor def process_data(size): import random data = [random.random() for _ in range(size)] return sorted(data) ```在使用装饰器时,有一些重要的最佳实践需要遵守。首先,总是使用 functools.wraps 来保留原函数的元信息,包括函数名、文档字符串等。这对于调试和反射操作非常重要。其次,避免在装饰器中修改被装饰函数的参数,除非这是装饰器的明确目的。最后,保持装饰器的功能单一,一个装饰器只做一件事,这样可以提高代码的可组合性和可测试性。
装饰器也可能带来一些性能开销,特别是在高频调用的场景中。如果发现装饰器成为性能瓶颈,可以考虑使用更高效的实现方式,或者在某些关键路径上绕过装饰器。此外,过度使用装饰器可能会让代码变得难以理解,要在简洁性和可读性之间找到平衡。
Python 装饰器是一个强大的工具,掌握其进阶应用可以让你写出更优雅、更可维护的代码。通过本文的示例和最佳实践,希望你能更好地理解和应用装饰器,在实际项目中发挥其价值。
