Python 装饰器实战技巧:让代码更优雅高效
装饰器是 Python 中最优雅的特性之一,它允许我们在不修改函数源代码的情况下,动态地增强函数的功能。这种"切面编程"的思想让代码更加模块化、可维护。本文将带你深入理解装饰器的各种用法和实战技巧。
一、装饰器基础原理
装饰器本质上是一个接受函数作为参数的高阶函数,它返回一个新的函数来替代原函数。这种语法糖让代码更加简洁直观。
import time
from functools import wraps
def timer(func):
"""计算函数执行时间的装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 执行时间: {end - start:.6f}秒")
return result
return wrapper
@timer
def slow_function():
"""模拟耗时操作"""
time.sleep(0.5)
return "完成"
# 调用
print(slow_function())输出结果:
slow_function 执行时间: 0.500321秒
完成这里的 @wraps(func) 装饰器非常重要,它会保留原函数的 __name__、__doc__ 等元数据,避免函数签名丢失。
二、参数化装饰器
有时我们需要为装饰器传递参数,比如设置重试次数、缓存大小等。这需要创建一个装饰器工厂函数。
def repeat(times=3):
"""重复执行函数指定次数"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(times=5)
def generate_random():
"""生成随机数"""
import random
return random.randint(1, 100)
print(f"生成结果: {generate_random()}")参数化装饰器的好处在于灵活性。我们可以根据不同需求配置装饰器行为:
def validate_types(*type_args, **type_kwargs):
"""参数类型验证装饰器"""
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},实际是 {type(arg)}"
)
# 验证关键字参数
for key, value in kwargs.items():
if key in type_kwargs:
if not isinstance(value, type_kwargs[key]):
raise TypeError(
f"参数 {key} 应该是 {type_kwargs[key]},实际是 {type(value)}"
)
return func(*args, **kwargs)
return wrapper
return decorator
@validate_types(int, str, name=str)
def process_data(id, title, name="default"):
"""处理数据"""
return f"ID: {id}, Title: {title}, Name: {name}"
print(process_data(1, "测试", name="小明"))
try:
process_data("错误", "测试") # 会抛出 TypeError
except TypeError as e:
print(f"类型错误: {e}")三、装饰器堆叠
Python 允许为同一个函数应用多个装饰器,装饰器的执行顺序是从下往上(由内向外)。
def log_calls(func):
"""记录函数调用"""
call_count = 0
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal call_count
call_count = 1
print(f"调用 #{call_count}: {func.__name__}({args})")
return func(*args, **kwargs)
return wrapper
def cache_result(func):
"""简单的缓存装饰器"""
cache = {}
@wraps(func)
def wrapper(*args, **kwargs):
key = str(args) str(sorted(kwargs.items()))
if key in cache:
print("从缓存读取")
return cache[key]
result = func(*args, **kwargs)
cache[key] = result
return result
return wrapper
@log_calls
@cache_result
@timer
def expensive_calculation(n):
"""模拟耗时计算"""
time.sleep(0.1)
return n ** 2
print(f"结果: {expensive_calculation(5)}")
print(f"结果: {expensive_calculation(5)}") # 从缓存读取
print(f"结果: {expensive_calculation(10)}")装饰器堆叠的强大之处在于可以组合多个独立的关注点,每个装饰器专注于一个特定功能。
四、类装饰器
除了函数装饰器,Python 还支持类装饰器。类装饰器通常用于修改类的行为或添加新功能。
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 Database:
"""数据库连接类"""
def __init__(self):
print("创建数据库连接实例")
self.connected = False
def connect(self):
if not self.connected:
print("连接数据库...")
self.connected = True
return self.connected
# 使用单例
db1 = Database()
db1.connect()
db2 = Database()
print(f"是否为同一实例: {db1 is db2}") # 输出: True另一个实用的类装饰器场景是自动添加方法:
def add_repr(cls):
"""自动添加 __repr__ 方法"""
original_init = cls.__init__
def __repr__(self):
attrs = []
for key, value in self.__dict__.items():
attrs.append(f"{key}={repr(value)}")
return f"{cls.__name__}({", ".join(attrs)})"
cls.__repr__ = __repr__
return cls
@add_repr
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
user = User("张三", 25, "zhangsan@example.com")
print(user) # 输出: User(name="张三", age=25, email="zhangsan@example.com")五、实战应用场景
1. 权限验证:在 Web 框架中,装饰器广泛用于路由保护和权限检查。
def require_permission(permission):
"""权限验证装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
current_user = kwargs.get("user")
if not current_user:
raise PermissionError("需要登录")
if permission not in current_user.get("permissions", []):
raise PermissionError(f"需要 {permission} 权限")
return func(*args, **kwargs)
return wrapper
return decorator
@require_permission("admin")
def delete_resource(resource_id, user=None):
return f"资源 {resource_id} 已删除"
# 模拟管理员用户
admin_user = {"name": "admin", "permissions": ["admin", "edit"]}
print(delete_resource(123, user=admin_user))2. API 请求重试:在网络请求中,自动重试失败的请求。
def retry(max_attempts=3, delay=1):
"""自动重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"第 { {attempt 1} 次尝试失败: {e}")
if attempt < max_attempts - 1:
time.sleep(delay)
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def unstable_api_call():
"""模拟不稳定的 API 调用"""
import random
if random.random() < 0.5: # 50% 失败率
raise ConnectionError("网络连接失败")
return "API 响应成功"
print(unstable_api_call())3. 性能监控:在生产环境中记录函数调用次数和性能指标。
class PerformanceMonitor:
"""性能监控类装饰器"""
def __init__(self, func):
self.func = func
self.call_count = 0
self.total_time = 0
wraps(func)(self)
def __call__(self, *args, **kwargs):
self.call_count = 1
start = time.perf_counter()
result = self.func(*args, **kwargs)
end = time.perf_counter()
self.total_time = (end - start)
return result
def stats(self):
avg_time = self.total_time / self.call_count if self.call_count else 0
return {
"name": self.func.__name__,
"calls": self.call_count,
"total_time": self.total_time,
"avg_time": avg_time
}
@PerformanceMonitor
def compute_factorial(n):
"""计算阶乘"""
if n <= 1:
return 1
return n * compute_factorial(n - 1)
compute_factorial(10)
compute_factorial(5)
print(compute_factorial.stats())六、最佳实践
1. 始终使用 functools.wraps:保留被装饰函数的元数据。
2. 保持装饰器简单:每个装饰器只做一件事,遵循单一职责原则。
3. 考虑性能影响:装饰器会增加函数调用开销,在性能敏感的代码中要谨慎使用。
4. 提供清晰的文档:为装饰器编写详细的文档字符串,说明其用途和参数。
5. 使用类装饰器管理状态:当需要在装饰器中维护状态时,类装饰器是更好的选择。
总结
装饰器是 Python 中体现"优雅胜于丑陋"哲学的完美示例。通过合理使用装饰器,我们可以将横切关注点(日志、缓存、验证、重试等)从业务逻辑中分离出来,让代码更加清晰、可维护。掌握装饰器技巧,将让你的 Python 代码上升到一个新的层次。
在实际项目中,建议从简单场景开始应用装饰器,逐步积累经验。当你发现某个功能需要在多个函数中重复时,就是考虑使用装饰器的好时机。记住,好的装饰器应该像语法糖一样自然,而不是增加代码复杂度的负担。
