Python 装饰器进阶:从原理到实战的完整指南
Python 装饰器是 Python 语言中最强大、最优雅的特性之一。它允许你在不修改函数源代码的情况下,动态地为函数添加额外的功能。本文将从装饰器的基本原理出发,深入探讨带参数的装饰器、类装饰器、装饰器链以及实战应用场景。
一、装饰器的基本原理
装饰器的本质是一个接受函数作为参数,并返回一个新函数的高阶函数。Python 使用 @ 语法糖来让装饰器的使用更加简洁优雅。
让我们从一个最简单的计时装饰器开始:
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f'函数 {func.__name__} 执行时间: {endend - start:.6f} 秒')
return result
return wrapper
@timer
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10))
在这个例子中,timer 函数接受被装饰的函数作为参数,返回一个新的 wrapper 函数。当我们调用 fibonacci(10) 时,实际上是在调用 wrapper 函数,它会记录执行时间并调用原始的 fibonacci 函数。
注意:使用 functools.wraps 装饰器可以保留原函数的元信息(如 __name__、__doc__ 等),这对于调试和文档生成非常重要。
二、带参数的装饰器
有时候我们需要让装饰器接受参数,这时需要创建一个装饰器工厂函数。这个工厂函数接受参数,返回一个真正的装饰器。
def repeat(times=2):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
result = func(*args, **kwargs)
results.append(result)
return results[-1] if times > 0 else None
return wrapper
return decorator
@repeat(times=3)
def say_hello(name):
print(f'你好, {name}!')
return f'问候了 {name}'
say_hello('小明')
这个 repeat 装饰器可以让函数执行指定的次数。这种三层结构(工厂函数 → 装饰器 → wrapper)是带参数装饰器的标准模式。
三、类装饰器
除了函数装饰器,Python 还支持类装饰器。类装饰器必须实现 __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.count}')
return self.func(*args, **kwargs)
@CountCalls
def process_data(data):
print(f'处理数据: {data}')
return data.upper()
print(process_data('hello'))
print(process_data('world'))
print(f'总调用次数: {process_data.count}')
类装饰器的优势是可以维护状态(如本例中的调用计数),这在某些场景下比函数装饰器更方便。
四、装饰器链
Python 允许一个函数应用多个装饰器,装饰器的执行顺序是从下到上(离函数定义最近的装饰器最先执行)。
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f'调用函数: {func.__name__}')
print(f'参数: args={args}, kwargs={kwargs}')
result = func(*args, **kwargs)
print(f'返回值: {result}')
return result
return wrapper
def cache_result(func):
cache = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = str(args) + str(kwargs)
if key not in cache:
cache[key] = func(*args, **kwargs)
print('计算并缓存结果')
else:
print('从缓存中获取结果')
return cache[key]
return wrapper
@log_calls
@cache_result
def expensive_calculation(x, y):
print('执行复杂计算...')
time.sleep(0.1)
return x ** 2 + y ** 2
print(expensive_calculation(3, 4))
print(expensive_calculation(3, 4))
print(expensive_calculation(5, 12))
在这个例子中,log_calls 会记录每次函数调用,而 cache_result 会缓存计算结果以提高性能。装饰器链让我们可以组合多个独立的功能模块。
五、实战应用:权限验证装饰器
在实际项目中,装饰器常用于权限验证、日志记录、性能监控等场景。下面是一个实用的权限验证装饰器:
class AuthenticationError(Exception):
pass
def require_permission(permission):
def decorator(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if not user.get('authenticated', False):
raise AuthenticationError('用户未登录')
if permission not in user.get('permissions', []):
raise AuthenticationError(f'缺少权限: {permission}')
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_permission('delete')
def delete_file(user, filename):
print(f'删除文件: {filename}')
return True
admin_user = {
'authenticated': True,
'permissions': ['read', 'write', 'delete']
}
guest_user = {
'authenticated': True,
'permissions': ['read']
}
delete_file(admin_user, 'test.txt')
try:
delete_file(guest_user, 'test.txt')
except AuthenticationError as e:
print(f'错误: {e}')
六、性能优化:装饰器实现缓存装饰器
对于纯函数(相同输入总是产生相同输出),我们可以使用装饰器实现缓存来大幅提升性能:
def memoize(max_size=128):
def decorator(func):
cache = {}
order = []
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = (args, frozenset(kwargs.items()))
if key in cache:
order.remove(key)
order.append(key)
return cache[key]
result = func(*args, **kwargs)
if len(cache) >= max_size:
oldest_key = order.pop(0)
del cache[oldest_key]
cache[key] = result
order.append(key)
return result
wrapper.cache_info = lambda: {'hits': len(cache), 'size': max_size}
return wrapper
return decorator
@memoize(max_size=100)
def factorial(n):
if n <= 1:
return 1
return n * factorial(n-1)
import time
start = time.time()
print(factorial(50))
print(f'缓存信息: {factorial.cache_info()}')
print(f'执行时间: {time.time() - start:.6f} 秒')
七、总结
Python 装饰器是一种优雅而强大的代码复用机制,它遵循 DRY(Don't Repeat Yourself)原则,让我们的代码更加简洁和可维护。关键要点:
- 装饰器本质是高阶函数,接受函数并返回新函数
- 使用 functools.wraps 保留原函数的元信息
- 带参数的装饰器需要三层结构(工厂 → 装饰器 → wrapper)
- 类装饰器适合需要维护状态的场景
- 装饰器链从下到上执行,可以组合多个功能
- 实战中常用于权限验证、缓存、日志、性能监控等场景
掌握装饰器将让你的 Python 代码更加 Pythonic,更具表现力和可维护性。
