Python装饰器实战指南:从入门到精通
Python 装饰器是许多开发者既熟悉又陌生的功能。熟悉是因为我们在框架中经常看到 @符号,陌生是因为很多人只是知其然不知其所以然。本文将从零开始,通过实际案例深入讲解装饰器的工作原理和应用场景。
装饰器的本质是一个函数,它接受一个函数作为参数,并返回一个新的函数。这种"高阶函数"的特性使得我们可以在不修改原函数代码的情况下,为函数添加额外功能。装饰器最常见的应用场景包括日志记录、性能计时、权限验证、缓存等。
让我们从一个最简单的装饰器开始。下面这个 timer_decorator 可以测量函数的执行时间:
import time
import functools
def timer_decorator(func):
@functools.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
@timer_decorator
def calculate_fibonacci(n):
"""计算斐波那契数列"""
if n <= 1:
return n
return calculate_fibonacci(n-1) + calculate_fibonacci(n-2)
calculate_fibonacci(30)
运行上面的代码,你会看到每个函数调用的执行时间被自动打印出来。这里有几个关键点需要注意:首先,我们使用 functools.wraps 来保留原函数的元信息(如函数名、文档字符串等);其次,wrapper 函数使用 *args 和 **kwargs 来接收任意参数,保证装饰器可以用于各种函数。
装饰器不仅接受函数参数,还可以接受自定义参数。这在需要配置装饰器行为时非常有用。例如,我们可以创建一个重试装饰器,指定重试次数和延迟时间:
import time
import functools
def retry(max_attempts=3, delay=1, exceptions=(Exception,)):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
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} 次尝试失败,{delay} 秒后重试...")
time.sleep(delay)
raise Exception(f"重试 {max_attempts} 次后仍然失败") from last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def unstable_api_call():
"""模拟不稳定的 API 调用"""
import random
if random.random() < 0.7:
raise ConnectionError("网络连接失败")
return {"status": "success", "data": "获取到数据"}
带参数的装饰器需要三层嵌套:最外层接收装饰器参数,中间层接收被装饰函数,最内层是实际的包装函数。这种模式虽然看起来复杂,但理解后就能灵活应用。
类装饰器是另一个强大的工具。与函数装饰器不同,类装饰器使用 __call__ 方法来实现装饰功能,并且可以保存状态。下面是一个计数装饰器的实现,可以记录函数被调用的次数:
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} 已被调用 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def process_data(data):
"""处理数据"""
return [x * 2 for x in data]
process_data([1, 2, 3])
process_data([4, 5, 6])
process_data([7, 8, 9])
类装饰器的优势在于可以维护状态。上面的例子中,每次调用函数时计数器都会增加。这种模式在需要跟踪函数调用历史的场景中非常有用。
装饰器在实际项目中的应用非常广泛。缓存装饰器可以显著提升性能,特别是在计算密集型或 I/O 密集型操作中。下面是一个简单的缓存装饰器实现:
def cache_decorator(max_size=128):
def decorator(func):
cache = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = str(args) + str(sorted(kwargs.items()))
if key in cache:
print(f"从缓存中获取: {func.__name__}({args}, {kwargs})")
return cache[key]
if len(cache) >= max_size:
cache.clear()
print("缓存已满,清空缓存")
result = func(*args, **kwargs)
cache[key] = result
return result
wrapper.cache = cache
return wrapper
return decorator
@cache_decorator(max_size=10)
def expensive_computation(x):
"""模拟耗时计算"""
time.sleep(0.1)
return x * x
print(expensive_computation(5))
print(expensive_computation(5))
print(expensive_computation(10))
装饰器可以叠加使用,执行顺序是从下到上。这个特性让我们可以组合多个装饰器来实现复杂功能。例如,我们可以同时使用日志、缓存和计时装饰器:
def log_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
result = func(*args, **kwargs)
print(f"函数返回: {result}")
return result
return wrapper
@timer_decorator
@cache_decorator(max_size=5)
@log_decorator
def complex_operation(n):
"""复杂操作示例"""
time.sleep(0.05)
return sum(range(n))
complex_operation(100)
complex_operation(100)
掌握装饰器是成为高级 Python 开发者的必经之路。通过本文的学习,你应该理解了装饰器的工作原理、带参数的装饰器、类装饰器以及实际应用场景。在实际项目中,合理使用装饰器可以让代码更加简洁、可维护、可扩展。记住,装饰器的核心思想是"在不修改原函数的情况下添加功能",这一理念在软件设计中非常重要。
