Python 装饰器完全指南:从原理到实战
Python 装饰器完全指南:从原理到实战
装饰器是 Python 中最优雅的设计模式之一,它允许我们在不修改原始函数代码的情况下,为函数添加额外的功能。本文将深入浅出地讲解装饰器的工作原理,并通过实际案例展示如何运用这一强大特性。
一、装饰器的基本概念
装饰器本质上是一个接受函数作为参数,并返回一个新函数的高阶函数。你可以把它想象成给函数"穿上外衣"的过程——函数本身不变,但获得了新的能力。
让我们从一个简单的例子开始:
import time
def timer_decorator(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
@timer_decorator
def calculate_sum(n):
"""计算 1 到 n 的和"""
total = 0
for i in range(1, n 1):
total = i
time.sleep(0.01) # 模拟耗时操作
return total
# 使用装饰器后的函数
result = calculate_sum(100)
print(f"计算结果: {result}")
运行这段代码,你会发现 calculate_sum 函数不仅返回了正确的计算结果,还自动输出了执行时间。这就是装饰器的魔力——我们没有修改 calculate_sum 的任何代码,却让它获得了计时功能。
二、装饰器的执行流程
理解装饰器的工作原理至关重要。当 Python 解释器遇到 @timer_decorator 时,实际上执行了以下操作:
# 这段代码:
@timer_decorator
def calculate_sum(n):
pass
# 等价于:
def calculate_sum(n):
pass
calculate_sum = timer_decorator(calculate_sum)
装饰器函数接收原始函数作为参数,返回一个新的 wrapper 函数。当我们调用被装饰的函数时,实际上调用的是 wrapper 函数,它在内部调用原始函数并添加额外逻辑。
三、带参数的装饰器
有时我们需要装饰器本身也能接受参数。这需要多一层函数嵌套:
def repeat_decorator(times):
"""重复执行函数指定次数的装饰器工厂"""
def decorator(func):
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
result = func(*args, **kwargs)
results.append(result)
return results[-1] # 返回最后一次执行的结果
return wrapper
return decorator
@repeat_decorator(times=3)
def process_data(data):
"""处理数据的函数"""
print(f"处理数据: {data}")
return data.upper()
result = process_data("hello")
print(f"最终结果: {result}")
这里 repeat_decorator(times) 返回一个真正的装饰器函数,这个装饰器再接收原始函数并返回 wrapper。这种三层嵌套的模式虽然看起来复杂,但理解了它的结构就很容易掌握。
四、类装饰器
除了函数装饰器,Python 还支持使用类作为装饰器。类装饰器通过实现 __call__ 方法来模拟函数行为:
class CacheDecorator:
"""简单的缓存装饰器"""
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if args in self.cache:
print("从缓存中获取结果")
return self.cache[args]
result = self.func(*args)
self.cache[args] = result
print("计算并缓存结果")
return result
@CacheDecorator
def fibonacci(n):
"""计算斐波那契数列"""
if n <= 1:
return n
return fibonacci(n - 1) fibonacci(n - 2)
# 测试缓存效果
print(f"fibonacci(10) = {fibonacci(10)}")
print(f"fibonacci(10) = {fibonacci(10)}") # 这次会从缓存获取
类装饰器的优势是可以保存状态(如这里的缓存字典),并且代码结构更清晰,适合复杂的功能实现。
五、实际应用案例
让我们看几个装饰器的实际应用场景:
1. 权限验证装饰器
class AuthDecorator:
"""权限验证装饰器"""
def __init__(self, required_role):
self.required_role = required_role
def __call__(self, func):
def wrapper(user, *args, **kwargs):
if user.get('role') != self.required_role:
raise PermissionError(f"需要 {self.required_role} 权限")
return func(user, *args, **kwargs)
return wrapper
@AuthDecorator(required_role='admin')
def delete_user(admin_user, user_id):
"""删除用户(仅管理员可操作)"""
print(f"删除用户 {user_id}")
return True
# 测试
try:
admin = {'role': 'admin', 'name': '张三'}
delete_user(admin, 123)
except PermissionError as e:
print(f"权限错误: {e}")
2. 日志记录装饰器
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def log_decorator(func):
"""日志记录装饰器"""
def wrapper(*args, **kwargs):
logger.info(f"调用函数: {func.__name__}, 参数: {args[1:]}")
try:
result = func(*args, **kwargs)
logger.info(f"函数 {func.__name__} 执行成功")
return result
except Exception as e:
logger.error(f"函数 {func.__name__} 执行失败: {str(e)}")
raise
return wrapper
@log_decorator
def divide_numbers(a, b):
"""除法运算"""
return a / b
# 测试
divide_numbers(10, 2)
try:
divide_numbers(10, 0)
except ZeroDivisionError:
pass
3. 重试机制装饰器
import random
def retry_decorator(max_attempts, delay=1):
"""失败重试装饰器"""
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
print(f"第 {attempt 1} 次尝试失败: {str(e)}")
time.sleep(delay)
return wrapper
return decorator
@retry_decorator(max_attempts=3, delay=2)
def unstable_operation():
"""模拟不稳定操作"""
if random.random() < 0.7:
raise ConnectionError("连接失败")
print("操作成功")
return True
# 测试重试机制
unstable_operation()
六、装饰器链
多个装饰器可以叠加使用,形成装饰器链。装饰器的执行顺序从下到上(从内到外):
def bold_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return f"**{result}**"
return wrapper
def italic_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return f"*{result}*"
return wrapper
@bold_decorator
@italic_decorator
def format_text(text):
return text
result = format_text("Hello World")
print(result) # 输出: ***Hello World***
七、使用 functools.wraps 保留元数据
装饰器的一个常见问题是会丢失原始函数的元数据(如函数名、文档字符串等)。我们可以使用 functools.wraps 来解决这个问题:
import functools
def proper_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""包装函数的文档"""
return func(*args, **kwargs)
return wrapper
@proper_decorator
def my_function():
"""原始函数的文档"""
pass
print(f"函数名: {my_function.__name__}")
print(f"文档: {my_function.__doc__}")
使用 @functools.wraps 后,wrapper 函数会继承原始函数的所有元数据,这对于调试和文档生成非常重要。
八、总结
装饰器是 Python 中体现"开放封闭原则"的完美实现——对扩展开放,对修改封闭。通过装饰器,我们可以:
- 在不修改原函数的情况下添加功能
- 将横切关注点(如日志、权限、缓存)与业务逻辑分离
- 提高代码的复用性和可维护性
- 让代码更加简洁优雅
掌握装饰器不仅能让你的 Python 代码更加专业,还能帮助你更好地理解函数式编程的思想。在实际项目中,合理使用装饰器可以大大提升代码质量和开发效率。
建议读者多动手实践,尝试编写自己的装饰器,并在项目中运用这些技巧。随着经验的积累,你会发现装饰器是一个强大的工具,能够帮助你写出更加优雅的 Python 代码。
