Python 装饰器完全指南:从入门到实战
Python 装饰器完全指南:从入门到实战
引言
在 Python 编程的世界里,装饰器(Decorator)是一个既强大又优雅的特性。它允许我们在不修改原有代码的前提下,为函数或类添加新的功能。这种"开放 - 封闭原则"的实现方式,使得代码更加模块化、可复用,也更易于维护。
无论你是刚接触 Python 的新手,还是已经有一定经验的开发者,深入理解装饰器都将为你的编程技能带来质的飞跃。本文将带你从装饰器的基础概念出发,逐步深入到高级用法和实际应用场景,最终掌握这一 Python 编程的利器。
一、装饰器的基本概念
1.1 什么是装饰器?
装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。通过装饰器,我们可以在不改变原函数代码的情况下,为其添加额外的功能。
想象一下,你有一个精美的相框(原函数),装饰器就像是在这个相框外面再套上一个更华丽的边框,而不需要改动相框本身。
1.2 为什么需要装饰器?
在实际开发中,我们经常遇到以下场景:
- 日志记录:需要在函数执行前后记录日志
- 性能测试:需要测量函数的执行时间
- 权限验证:需要在函数执行前检查用户权限
- 缓存优化:需要缓存函数的返回值以提高性能
- 事务管理:需要在函数执行前后处理数据库事务
如果没有装饰器,我们可能需要在每个函数中重复编写相同的代码。而装饰器让我们能够将这些通用逻辑提取出来,实现代码的复用。
二、函数装饰器详解
2.1 最简单的装饰器
让我们从一个最简单的装饰器开始:
def simple_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@simple_decorator
def say_hello():
print("Hello, World!")
say_hello()
输出结果:
函数执行前
Hello, World!
函数执行后
这里的 INLINE_CODE_0 语法糖等价于 INLINE_CODE_1。
2.2 保留原函数信息
上面的例子有一个问题:装饰后的函数会丢失原有的名称和文档字符串。为了解决这个问题,我们可以使用 INLINE_CODE_2:
from functools import wraps
def decorator_with_wraps(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("执行前准备")
result = func(*args, **kwargs)
print("执行后清理")
return result
return wrapper
@decorator_with_wraps
def greet(name, greeting="Hello"):
"""向用户打招呼"""
print(f"{greeting}, {name}!")
return name
print(greet.__name__) # 输出:greet
print(greet.__doc__) # 输出:向用户打招呼
使用 INLINE_CODE_3 可以确保装饰后的函数保留原有的 INLINE_CODE_4、INLINE_CODE_5 等属性。
2.3 带参数的装饰器
有时候我们需要给装饰器本身传递参数,这就需要三层嵌套:
def repeat(times):
"""重复执行函数的装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(times):
print(f"第 {i + 1} 次执行:")
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
say_hi()
输出:
第 1 次执行:
Hi!
第 2 次执行:
Hi!
第 3 次执行:
Hi!
三、类装饰器
装饰器不仅可以装饰函数,还可以装饰类。类装饰器接受一个类作为参数,并返回一个新的类。
def singleton(cls):
"""单例模式装饰器"""
instances = {}
@wraps(cls)
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class DatabaseConnection:
def __init__(self):
print("创建数据库连接")
def query(self, sql):
print(f"执行查询:{sql}")
# 无论创建多少次,都只会有一个实例
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # 输出:True
四、实际应用场景
4.1 日志记录装饰器
import logging
from datetime import datetime
logging.basicConfig(level=logging.INFO)
def log_execution(func):
"""记录函数执行的日志装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"开始执行 {func.__name__}, 参数:args={args}, kwargs={kwargs}")
start_time = datetime.now()
try:
result = func(*args, **kwargs)
logging.info(f"{func.__name__} 执行成功")
return result
except Exception as e:
logging.error(f"{func.__name__} 执行失败:{str(e)}")
raise
finally:
end_time = datetime.now()
duration = (end_time - start_time).total_seconds()
logging.info(f"{func.__name__} 执行耗时:{duration:.4f}秒")
return wrapper
@log_execution
def calculate_sum(numbers):
return sum(numbers)
calculate_sum([1, 2, 3, 4, 5])
4.2 缓存装饰器(记忆化)
def memoize(func):
"""缓存函数结果的装饰器"""
cache = {}
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
print(f"计算 {args} 的结果并缓存")
else:
print(f"从缓存获取 {args} 的结果")
return cache[args]
return wrapper
@memoize
def fibonacci(n):
"""计算斐波那契数列"""
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 会缓存中间结果
print(fibonacci(10)) # 直接从缓存获取
4.3 权限验证装饰器
def require_permission(required_role):
"""权限验证装饰器"""
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if user.get('role') != required_role:
raise PermissionError(
f"权限不足:需要 {required_role} 角色,"
f"当前角色为 {user.get('role')}"
)
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_permission('admin')
def delete_user(user, target_user_id):
print(f"管理员 {user['name']} 删除了用户 {target_user_id}")
# 测试
admin_user = {'name': 'Alice', 'role': 'admin'}
normal_user = {'name': 'Bob', 'role': 'user'}
delete_user(admin_user, 123) # 成功
# delete_user(normal_user, 123) # 抛出 PermissionError
4.4 重试装饰器
import time
import random
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=0.5)
def unstable_operation():
"""模拟不稳定的操作"""
if random.random() < 0.7:
raise ConnectionError("网络连接失败")
return "操作成功"
try:
result = unstable_operation()
print(result)
except Exception as e:
print(f"最终失败:{e}")
4.5 性能分析装饰器
import time
from collections import defaultdict
class PerformanceAnalyzer:
"""性能分析装饰器类"""
stats = defaultdict(list)
@classmethod
def report(cls):
print("\n=== 性能分析报告 ===")
for func_name, times in cls.stats.items():
avg_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
print(f"{func_name}:")
print(f" 调用次数:{len(times)}")
print(f" 平均耗时:{avg_time:.4f}秒")
print(f" 最短耗时:{min_time:.4f}秒")
print(f" 最长耗时:{max_time:.4f}秒")
def profile(func):
"""性能分析装饰器"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
duration = end - start
PerformanceAnalyzer.stats[func.__name__].append(duration)
return result
return wrapper
@profile
def slow_function():
time.sleep(0.1)
@profile
def fast_function():
time.sleep(0.01)
for _ in range(5):
slow_function()
fast_function()
PerformanceAnalyzer.report()
五、装饰器链与执行顺序
多个装饰器可以叠加使用,执行顺序是从下往上(靠近函数定义的先执行):
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 test_function():
print("原函数执行")
test_function()
输出:
装饰器 A - 前
装饰器 B - 前
原函数执行
装饰器 B - 后
装饰器 A - 后
六、最佳实践与注意事项
6.1 使用 functools.wraps
始终使用 INLINE_CODE_6 来保留原函数的元信息,这对于调试和文档生成非常重要。
6.2 保持装饰器单一职责
每个装饰器应该只负责一个功能,这样便于组合和测试。
6.3 注意装饰器的性能开销
装饰器会增加函数调用的开销,在性能敏感的代码中需要谨慎使用。
6.4 避免过度嵌套
过深的嵌套会让代码难以理解和维护,必要时可以考虑使用类来实现装饰器。
6.5 文档化装饰器
为装饰器编写清晰的文档字符串,说明其用途、参数和返回值。
七、总结
装饰器是 Python 中最优雅和强大的特性之一。通过本文的学习,你应该已经掌握了:
- 装饰器的基本概念和工作原理
- 如何创建简单的函数装饰器
- 如何使用 functools.wraps 保留函数信息
- 如何创建带参数的装饰器
- 类装饰器的实现方法
- 多种实际应用场景的实现
- 装饰器链的执行顺序
- 装饰器的最佳实践
装饰器的学习曲线可能有些陡峭,但一旦掌握,它将极大地提升你的代码质量和开发效率。建议你在实际项目中多加练习,从简单的日志记录开始,逐步尝试更复杂的场景。
记住,好的装饰器应该是透明的、可组合的,并且遵循单一职责原则。当你发现自己在多个函数中重复相同的代码模式时,就是考虑使用装饰器的时候了。
希望这篇教程能够帮助你更好地理解和运用 Python 装饰器。祝你在编程的道路上越走越远!