Python 装饰器完全指南:从入门到实战
Python 装饰器完全指南:从入门到实战
引言
在 Python 编程世界中,装饰器(Decorator)是最强大也最容易被误解的特性之一。它允许你在不修改原函数代码的前提下,动态地增强或修改函数的行为。本文将带你从基础概念到高级应用,全面掌握 Python 装饰器的使用技巧。
什么是装饰器?
装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。这种设计模式源于函数式编程中的"包装器"概念,让你能够在保持原函数功能的同时,添加额外的逻辑。
想象一下,你有一批礼物(函数),装饰器就是包装纸和丝带——它不改变礼物本身,但让礼物看起来更精美,功能更丰富。
基础语法与原理
最简单的装饰器
让我们从一个最基础的例子开始:
def simple_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@simple_decorator
def say_hello():
print("Hello!")
say_hello()
输出结果:
函数执行前
Hello!
函数执行后
这里的 INLINE_CODE_0 语法糖等价于 INLINE_CODE_1。装饰器在函数定义时立即执行,将原函数替换为包装后的新函数。
保留函数元信息
上述基础版本存在一个问题:原函数的名称、文档字符串等元信息会丢失。使用 INLINE_CODE_2 可以解决这个问题:
from functools import wraps
def preserve_metadata(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@preserve_metadata
def documented_function():
"""这是一个有文档的函数"""
pass
print(documented_function.__name__) # 输出:documented_function
print(documented_function.__doc__) # 输出:这是一个有文档的函数
带参数的装饰器
实际应用中,我们经常需要给装饰器本身传递参数。这需要三层嵌套:
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("World")
输出:
Hello, World!
Hello, World!
Hello, World!
实用场景与代码示例
1. 性能计时器
在性能优化中,我们经常需要测量函数执行时间:
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_operation():
time.sleep(1)
return "完成"
slow_operation()
2. 重试机制
网络请求或文件操作可能因临时故障失败,自动重试能提高系统稳定性:
import random
from functools import wraps
def retry(max_attempts=3, delay=1):
def decorator(func):
@wraps(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} 次失败:{e},{delay}秒后重试...")
time.sleep(delay)
return None
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def unstable_operation():
if random.random() < 0.7:
raise ConnectionError("网络连接失败")
return "操作成功"
3. 权限验证
在 Web 应用中,装饰器常用于权限控制:
from functools import wraps
def require_login(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not user.is_authenticated:
raise PermissionError("用户未登录")
return func(user, *args, **kwargs)
return wrapper
def require_admin(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if not user.is_admin:
raise PermissionError("需要管理员权限")
return func(user, *args, **kwargs)
return wrapper
class User:
def __init__(self, name, is_authenticated=True, is_admin=False):
self.name = name
self.is_authenticated = is_authenticated
self.is_admin = is_admin
@require_login
@require_admin
def delete_user(admin_user, target_user):
print(f"管理员 {admin_user.name} 删除了用户 {target_user}")
return True
4. 缓存装饰器
对于计算密集型或 I/O 密集型操作,缓存结果能显著提升性能:
from functools import wraps
def cache(func):
cache_storage = {}
@wraps(func)
def wrapper(*args):
if args in cache_storage:
print(f"从缓存获取 {func.__name__}{args}")
return cache_storage[args]
print(f"计算 {func.__name__}{args}")
result = func(*args)
cache_storage[args] = result
return result
return wrapper
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 第一次计算
print(fibonacci(10)) # 从缓存获取
5. 日志记录
在生产环境中,记录函数调用信息对调试和监控至关重要:
import logging
from datetime import datetime
from functools import wraps
logging.basicConfig(level=logging.INFO)
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
timestamp = datetime.now().isoformat()
logging.info(f"[{timestamp}] 调用 {func.__name__}, 参数:args={args}, kwargs={kwargs}")
try:
result = func(*args, **kwargs)
logging.info(f"[{timestamp}] {func.__name__} 返回:{result}")
return result
except Exception as e:
logging.error(f"[{timestamp}] {func.__name__} 异常:{e}")
raise
return wrapper
@log_calls
def divide(a, b):
return a / b
divide(10, 2)
类装饰器
装饰器不仅可以是函数,也可以是类。类装饰器通过实现 INLINE_CODE_3 方法来工作:
class CountCalls:
def __init__(self, func):
wraps(func)(self)
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
print(f"第 {self.call_count} 次调用 {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
greet("Bob")
print(f"总调用次数:{greet.call_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 test_function():
print("原函数执行")
test_function()
输出:
装饰器 A - 前
装饰器 B - 前
原函数执行
装饰器 B - 后
装饰器 A - 后
实际项目中的应用建议
1. 选择合适的抽象层级
装饰器应该专注于单一职责。不要在一个装饰器中混合日志、缓存、权限验证等多个功能。每个装饰器只做一件事,并做好。
2. 注意性能开销
装饰器会引入额外的函数调用开销。在性能敏感的代码路径中,谨慎使用多层装饰器。对于高频调用的函数,考虑内联装饰器逻辑。
3. 保持可测试性
装饰器应该易于单元测试。避免在装饰器中依赖全局状态,使用依赖注入让测试更简单。
4. 文档化装饰器行为
使用装饰器的函数行为可能不明显。在文档中说明装饰器的效果,帮助其他开发者理解代码。
常见陷阱与解决方案
陷阱 1:装饰器不保留函数签名
问题:IDE 无法正确提示参数,工具无法分析函数签名。
解决:始终使用 INLINE_CODE_4,或在 Python 3.3+ 中使用 INLINE_CODE_5 保留签名信息。
陷阱 2:装饰器在模块加载时执行
问题:装饰器在导入模块时就执行,可能导致意外副作用。
解决:确保装饰器逻辑是惰性的,只在被装饰函数被调用时才执行。
陷阱 3:方法装饰器与 self 参数
问题:类方法装饰器需要正确处理 INLINE_CODE_6 参数。
解决:使用 INLINE_CODE_7 通配参数,或专门编写类方法装饰器。
结语
Python 装饰器是提升代码质量、减少重复、增强可维护性的强大工具。从简单的日志记录到复杂的权限控制,装饰器模式在现代 Python 开发中无处不在。
掌握装饰器的关键在于理解其本质:函数包装器。一旦理解了这个核心概念,你就能灵活运用装饰器解决各种实际问题。
建议的学习路径:
- 从基础语法开始,手动实现几个简单装饰器
- 理解 INLINE_CODE_8 的重要性并养成使用习惯
- 掌握带参数装饰器的三层嵌套模式
- 在实际项目中应用,从日志、计时等简单场景开始
- 逐步探索类装饰器、装饰器栈等高级用法
记住,好的装饰器应该让代码更清晰,而不是更复杂。当你发现装饰器让代码难以理解时,也许是时候重新设计了。
关于作者:本文是 Python 进阶系列教程的一部分,旨在帮助开发者深入理解 Python 的核心特性。欢迎在实践中探索更多装饰器的创意用法。