Python装饰器实战:15个实用案例详解
Python装饰器是我最喜欢的语言特性之一,它让我们能够优雅地修改或增强函数和类的行为,而无需修改其源代码。在这篇文章中,我将分享15个实用的装饰器案例,帮助你更好地理解和应用这一强大的功能。
装饰器基础
在深入案例之前,让我们快速回顾一下装饰器的基本概念:
1 2 3 4 5 6 7 8 9 10 11
| def my_decorator(func): def wrapper(*args, **kwargs): print("在函数调用前执行") result = func(*args, **kwargs) print("在函数调用后执行") return result return wrapper
@my_decorator def say_hello(name): print(f"Hello, {name}")
|
现在,让我们看看一些实用的装饰器案例。
1. 计时装饰器
测量函数执行时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import time import functools
def timer(func): @functools.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:.6f}秒") return result return wrapper
@timer def slow_function(): time.sleep(1) return "完成"
|
2. 重试装饰器
自动重试可能失败的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import time import functools import random
def retry(max_attempts=3, delay=1): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): attempts = 0 while attempts < max_attempts: try: return func(*args, **kwargs) except Exception as e: attempts += 1 if attempts == max_attempts: raise print(f"尝试 {attempts}/{max_attempts} 失败: {e}. 等待 {delay} 秒后重试...") time.sleep(delay) return wrapper return decorator
@retry(max_attempts=5, delay=2) def unstable_network_call(): if random.random() < 0.7: raise ConnectionError("网络连接失败") return "成功获取数据"
|
3. 缓存装饰器
缓存函数结果以提高性能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import functools
def memoize(func): cache = {} @functools.wraps(func) def wrapper(*args, **kwargs): key_kwargs = tuple(sorted(kwargs.items())) key = (args, key_kwargs) if key not in cache: cache[key] = func(*args, **kwargs) return cache[key] return wrapper
@memoize def fibonacci(n): if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2)
|
4. 参数验证装饰器
验证函数参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import functools
def validate_types(**expected_types): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): func_code = func.__code__ param_names = func_code.co_varnames[:func_code.co_argcount] all_args = dict(zip(param_names, args)) all_args.update(kwargs) for param, expected_type in expected_types.items(): if param in all_args: actual_value = all_args[param] if not isinstance(actual_value, expected_type): raise TypeError(f"参数 '{param}' 必须是 {expected_type.__name__} 类型, " f"但收到的是 {type(actual_value).__name__}") return func(*args, **kwargs) return wrapper return decorator
@validate_types(name=str, age=int) def greet(name, age): return f"你好, {name}! 你已经 {age} 岁了。"
|
5. 日志装饰器
记录函数调用和结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import functools import logging
logging.basicConfig(level=logging.INFO)
def log_function(func): @functools.wraps(func) def wrapper(*args, **kwargs): args_repr = [repr(a) for a in args] kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()] signature = ", ".join(args_repr + kwargs_repr) logging.info(f"调用 {func.__name__}({signature})") try: result = func(*args, **kwargs) logging.info(f"{func.__name__} 返回: {result!r}") return result except Exception as e: logging.exception(f"{func.__name__} 抛出异常: {e}") raise return wrapper
@log_function def divide(a, b): return a / b
|
6. 单例装饰器
确保类只有一个实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| def singleton(cls): instances = {} @functools.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, host, username, password): self.host = host self.username = username self.password = password print(f"连接到数据库 {host}")
|
7. 权限检查装饰器
检查用户权限:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import functools
def require_permission(permission): def decorator(func): @functools.wraps(func) def wrapper(user, *args, **kwargs): if permission not in user.permissions: raise PermissionError(f"用户 {user.username} 没有 {permission} 权限") return func(user, *args, **kwargs) return wrapper return decorator
class User: def __init__(self, username, permissions=None): self.username = username self.permissions = permissions or []
@require_permission("admin") def delete_user(current_user, user_id): print(f"{current_user.username} 删除了用户 {user_id}")
|
8. 限速装饰器
限制函数调用频率:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import time import functools from collections import deque
def rate_limit(max_calls, period): """限制函数在指定时间段内的最大调用次数""" calls = deque(maxlen=max_calls) def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): now = time.time() while calls and calls[0] < now - period: calls.popleft() if len(calls) >= max_calls: wait_time = calls[0] + period - now raise Exception(f"调用频率过高,请在 {wait_time:.2f} 秒后重试") result = func(*args, **kwargs) calls.append(now) return result return wrapper return decorator
@rate_limit(max_calls=3, period=60) def send_api_request(endpoint): print(f"发送请求到 {endpoint}")
|
9. 超时装饰器
限制函数执行时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import signal import functools
class TimeoutError(Exception): pass
def timeout(seconds): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): def handler(signum, frame): raise TimeoutError(f"函数执行超过 {seconds} 秒") original_handler = signal.signal(signal.SIGALRM, handler) signal.alarm(seconds) try: result = func(*args, **kwargs) finally: signal.alarm(0) signal.signal(signal.SIGALRM, original_handler) return result return wrapper return decorator
@timeout(5) def long_running_task(): import time time.sleep(10)
|
10. 异步重试装饰器
异步函数的重试机制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import asyncio import functools
def async_retry(max_attempts=3, delay=1): def decorator(func): @functools.wraps(func) async def wrapper(*args, **kwargs): attempts = 0 while attempts < max_attempts: try: return await func(*args, **kwargs) except Exception as e: attempts += 1 if attempts == max_attempts: raise print(f"尝试 {attempts}/{max_attempts} 失败: {e}. 等待 {delay} 秒后重试...") await asyncio.sleep(delay) return wrapper return decorator
@async_retry(max_attempts=3, delay=2) async def fetch_data(url): if random.random() < 0.7: raise ConnectionError("网络连接失败") return f"来自 {url} 的数据"
|
11. 参数转换装饰器
自动转换函数参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import functools
def convert_args(**converters): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): func_code = func.__code__ param_names = func_code.co_varnames[:func_code.co_argcount] converted_args = list(args) for i, param_name in enumerate(param_names[:len(args)]): if param_name in converters: converted_args[i] = converters[param_name](args[i]) converted_kwargs = {} for key, value in kwargs.items(): if key in converters: converted_kwargs[key] = converters[key](value) else: converted_kwargs[key] = value return func(*converted_args, **converted_kwargs) return wrapper return decorator
@convert_args(age=int, height=float) def register_user(name, age, height): print(f"注册用户: {name}, 年龄: {age} ({type(age)}), 身高: {height} ({type(height)})")
register_user("Alice", "30", "165.5")
|
12. 属性缓存装饰器
缓存类属性计算结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def cached_property(func): attr_name = f"_{func.__name__}" @property @functools.wraps(func) def wrapper(self): if not hasattr(self, attr_name): setattr(self, attr_name, func(self)) return getattr(self, attr_name) return wrapper
class Circle: def __init__(self, radius): self.radius = radius @cached_property def area(self): print("计算面积...") import math return math.pi * self.radius ** 2
|
13. 弃用警告装饰器
标记弃用的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import functools import warnings
def deprecated(reason): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): warnings.warn( f"{func.__name__} 已弃用: {reason}", category=DeprecationWarning, stacklevel=2 ) return func(*args, **kwargs) return wrapper return decorator
@deprecated("请使用 new_function() 代替") def old_function(): return "旧功能"
|
14. 事件触发装饰器
在函数执行前后触发事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import functools
class EventSystem: def __init__(self): self.listeners = {} def subscribe(self, event, callback): if event not in self.listeners: self.listeners[event] = [] self.listeners[event].append(callback) def emit(self, event, *args, **kwargs): if event in self.listeners: for callback in self.listeners[event]: callback(*args, **kwargs)
event_system = EventSystem()
def emit_events(before_event=None, after_event=None): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): if before_event: event_system.emit(before_event, *args, **kwargs) result = func(*args, **kwargs) if after_event: event_system.emit(after_event, result, *args, **kwargs) return result return wrapper return decorator
@emit_events(before_event="user_login_attempt", after_event="user_login_complete") def login_user(username, password): return {"username": username, "success": True}
|
15. 参数注入装饰器
自动注入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import functools import inspect
class DependencyContainer: def __init__(self): self.dependencies = {} def register(self, name, instance): self.dependencies[name] = instance def get(self, name): return self.dependencies.get(name)
container = DependencyContainer()
def inject(**dependencies): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): sig = inspect.signature(func) for param_name, dependency_name in dependencies.items(): if param_name in sig.parameters and param_name not in kwargs: kwargs[param_name] = container.get(dependency_name) return func(*args, **kwargs) return wrapper return decorator
container.register("db", {"connection": "sqlite:///:memory:"}) container.register("logger", {"log": lambda msg: print(f"LOG: {msg}")})
@inject(db="db", logger="logger") def save_user(user, db, logger): logger["log"](f"保存用户到数据库: {user}") return True
|
结论
装饰器是Python中非常强大的特性,可以帮助我们编写更简洁、更可维护的代码。通过这15个实用案例,你应该能够更好地理解装饰器的工作原理和应用场景。
记住,好的装饰器应该遵循单一职责原则,专注于解决一个特定的问题。此外,使用functools.wraps来保留原始函数的元数据也是一个好习惯。
你有什么创新的装饰器用例吗?欢迎在评论区分享!