Python 装饰器实用指南:从入门到精通
装饰器本质上是一个接受函数作为参数并返回新函数的高阶函数。这个概念听起来有些抽象,但让我们通过一个具体的例子来理解它的实际价值。
想象一下,你正在开发一个 Web 应用,需要记录每个函数的执行时间。如果没有装饰器,你可能需要在每个函数中手动添加计时代码。这不仅重复,而且违反了 DRY(Don't Repeat Yourself)原则。装饰器正是解决这类问题的优雅方案。
一、装饰器的基础语法
让我们从一个最简单的装饰器开始:
def timer_decorator(func):
"""计算函数执行时间的装饰器"""
import time
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在这个例子中,@timer_decorator 语法糖等价于 calculate_sum = timer_decorator(calculate_sum)。装饰器接收原函数,返回一个包装后的新函数,在调用前后添加了计时逻辑。
二、保留函数元数据
上面的例子有一个小问题:装饰后的函数会丢失原有的名称和文档字符串。Python 的 functools.wraps 可以解决这个问题:
from functools import wraps
import time
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使用 @wraps(func) 可以确保装饰后的函数保留原有的名称、文档字符串等元数据,这对于调试和代码可读性非常重要。
三、带参数的装饰器
实际开发中,我们经常需要给装饰器传递参数。这需要再增加一层函数嵌套:
def repeat(times):
"""重复执行函数的装饰器,可指定重复次数"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
results = []
for i in range(times):
print(f"第 {i + 1} 次执行...")
result = func(*args, **kwargs)
results.append(result)
return results
return wrapper
return decorator注意装饰器的三层结构:最外层接收装饰器参数,中间层接收被装饰的函数,最内层是实际执行的包装函数。这种模式在编写灵活的装饰器时非常常见。
四、实用装饰器案例
1. 重试装饰器
在网络请求或不稳定操作中,自动重试是非常有用的功能:
def retry(max_attempts=3, delay=1, backoff=2, exceptions=(Exception,)):
"""
失败自动重试装饰器
参数:
max_attempts: 最大重试次数
delay: 初始等待时间(秒)
backoff: 等待时间倍增系数
exceptions: 需要捕获的异常类型
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
current_delay = delay
last_exception = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except exceptions as e:
last_exception = e
if attempt < max_attempts - 1:
print(f"尝试 {attempt + 1} 失败:{e}")
print(f"等待 {current_delay} 秒后重试...")
time.sleep(current_delay)
current_delay *= backoff
else:
print(f"达到最大重试次数 {max_attempts}")
raise last_exception
return wrapper
return decorator2. 缓存装饰器
对于计算密集型函数,缓存结果可以显著提升性能:
def cache(max_size=128):
"""简单的 LRU 缓存装饰器"""
def decorator(func):
cache_dict = {}
cache_order = []
@wraps(func)
def wrapper(*args):
if args in cache_dict:
print(f"缓存命中:{args}")
cache_order.remove(args)
cache_order.append(args)
return cache_dict[args]
print(f"缓存未命中,计算:{args}")
result = func(*args)
cache_dict[args] = result
cache_order.append(args)
if len(cache_dict) > max_size:
oldest = cache_order.pop(0)
del cache_dict[oldest]
print(f"缓存已满,移除最旧项:{oldest}")
return result
wrapper.cache_clear = lambda: cache_dict.clear() or cache_order.clear()
wrapper.cache_info = lambda: f"缓存大小:{len(cache_dict)}/{max_size}"
return wrapper
return decorator3. 权限验证装饰器
在 Web 应用中,权限验证是常见需求:
def require_permission(required_role):
"""权限验证装饰器"""
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not hasattr(user, 'role'):
raise PermissionError("用户没有角色属性")
if user.role != required_role:
raise PermissionError(
f"权限不足:需要 {required_role},当前为 {user.role}"
)
print(f"权限验证通过:{user.name} ({user.role})")
return func(user, *args, **kwargs)
return wrapper
return decorator五、类装饰器
装饰器不仅可以装饰函数,还可以装饰类。类装饰器接收一个类作为参数,返回一个新的类:
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 DatabaseConnection:
def __init__(self):
self.connection_count = 0
def connect(self):
self.connection_count += 1
return f"连接 #{self.connection_count}"六、装饰器链
多个装饰器可以叠加使用,执行顺序是从下往上:
def decorator_a(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("[A] 前置处理")
result = func(*args, **kwargs)
print("[A] 后置处理")
return result
return wrapper
def decorator_b(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("[B] 前置处理")
result = func(*args, **kwargs)
print("[B] 后置处理")
return result
return wrapper
@decorator_a
@decorator_b
def say_hello():
print("Hello!")七、最佳实践与注意事项
1. 始终使用 @wraps:保留原函数的元数据,便于调试和文档生成。
2. 保持装饰器单一职责:每个装饰器只做一件事,这样更容易测试和维护。
3. 注意性能影响:装饰器会增加函数调用开销,在性能敏感的场景中需要权衡。
4. 避免过度嵌套:如果装饰器逻辑过于复杂,考虑将其拆分为多个简单的装饰器。
5. 文档化装饰器:清晰的文档字符串帮助其他开发者理解装饰器的用途和参数。
总结
装饰器是 Python 中强大的工具,它让你能够以优雅的方式扩展函数功能。从简单的计时器到复杂的重试机制,装饰器的应用场景非常广泛。掌握装饰器的关键在于理解其本质——高阶函数,以及熟练运用函数嵌套和闭包的概念。
希望本文能帮助你更好地理解和应用装饰器。在实际项目中,不妨尝试编写自己的装饰器,你会发现代码变得更加简洁和可维护。
