Python 装饰器的高级用法与设计模式
Python 装饰器(Decorator)是语言中最优雅的特性之一,它允许我们以声明式的方式修改或增强函数的行为。本文将深入探讨装饰器的高级用法,包括带参数的装饰器、类装饰器、装饰器链、以及实际开发中的设计模式应用。
一、装饰器基础回顾
装饰器的本质是一个接受函数作为参数并返回新函数的高阶函数。最简单的装饰器示例:
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 耗时: {end - start:.4f}秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(0.1)
return "完成"
slow_function() # 输出: slow_function 耗时: 0.1001秒
但基础的装饰器有一些问题:丢失了原函数的元数据(__name__, __doc__等),且无法接受参数。让我们逐步解决这些问题。
二、保持函数元数据
使用 functools.wraps 可以保留原函数的元信息:
from functools import wraps
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call
def calculate(x, y):
"""计算两个数的和"""
return x + y
print(calculate.__name__) # 输出: calculate
print(calculate.__doc__) # 输出: 计算两个数的和
三、带参数的装饰器工厂
当装饰器需要接受参数时,我们需要使用三层嵌套:外层接收参数,中层返回装饰器,内层包装函数:
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
results.append(func(*args, **kwargs))
return results[-1] # 返回最后一次调用的结果
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"你好, {name}!")
return f"greeted_{name}"
greet("小明") # 输出三遍问候
这种模式被称为"装饰器工厂"——它创建并返回真正的装饰器。
四、类装饰器
装饰器也可以用于类。类装饰器可以动态修改类的属性或方法:
def add_str_method(cls):
"""为类添加 __str__ 方法"""
def __str__(self):
items = {k: v for k, v in self.__dict__.items()
if not k.startswith('_')}
return f"{cls.__name__}({items})"
cls.__str__ = __str__
return cls
@add_str_method
class User:
def __init__(self, name, age):
self.name = name
self.age = age
user = User("张三", 25)
print(user) # 输出: User({'name': '张三', 'age': 25})
类装饰器常用于注册类(如 ORM 模型注册)、添加通用方法、或修改类行为。
五、装饰器链与执行顺序
多个装饰器可以堆叠使用,执行顺序从下往上(从内到外):
def uppercase(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper() if isinstance(result, str) else result
return wrapper
def exclamation(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return f"{result}!"
return wrapper
@exclamation
@uppercase
def say_hello():
return "hello world"
print(say_hello()) # 输出: HELLO WORLD!
# 执行顺序: say_hello -> uppercase -> exclamation
理解装饰器链的顺序对调试装饰器行为至关重要。
六、单例模式装饰器
装饰器是实现设计模式的利器。下面是一个线程安全的单例模式:
import threading
def singleton(cls):
instances = {}
lock = threading.Lock()
@wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
with lock:
if cls not in instances: # 双重检查锁定
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self):
print("创建数据库连接")
self.connection = "模拟连接"
db1 = Database()
db2 = Database()
print(db1 is db2) # 输出: True
这个实现使用了双重检查锁定(Double-Checked Locking)确保线程安全。
七、缓存装饰器
Python 的 functools.lru_cache 是内置的缓存装饰器,我们也可以实现自定义版本:
def memoize(max_size=128):
"""带大小限制的记忆化装饰器"""
def decorator(func):
cache = {}
@wraps(func)
def wrapper(*args, **kwargs):
key = (args, tuple(sorted(kwargs.items())))
if key in cache:
return cache[key]
if len(cache) >= max_size:
cache.popitem() # 删除最旧的条目
result = func(*args, **kwargs)
cache[key] = result
return result
wrapper.cache = cache # 暴露缓存用于调试
return wrapper
return decorator
@memoize(max_size=3)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100)) # 快速计算大数
这种记忆化技术可以将递归算法的时间复杂度从 O(2^n) 降到 O(n)。
八、权限验证装饰器
在 Web 开发中,装饰器常用于权限检查:
def require_permission(permission):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
current_user = kwargs.get('user')
if not current_user or permission not in current_user.permissions:
raise PermissionError(f"需要权限: {permission}")
return func(*args, **kwargs)
return wrapper
return decorator
class User:
def __init__(self, permissions):
self.permissions = permissions
@require_permission('admin')
def delete_user(user_id, user=None):
print(f"删除用户 {user_id}")
admin = User(['admin', 'editor'])
delete_user(123, user=admin) # 正常执行
guest = User(['guest'])
# delete_user(456, user=guest) # 抛出 PermissionError
九、装饰器的性能考虑
装饰器会引入额外的函数调用开销。对于性能敏感的场景,可以考虑:
import functools
def fast_timer(func):
"""使用 functools.partial 减少闭包开销"""
@functools.wraps(func)
def wrapper(*args, _func=func, **kwargs):
start = time.perf_counter()
result = _func(*args, **kwargs)
print(f"耗时: {time.perf_counter() - start:.4f}")
return result
return wrapper
十、最佳实践总结
1. 始终使用 functools.wraps 保留函数元数据
2. 装饰器应该保持透明——包装函数的签名应与原函数一致
3. 避免在装饰器中修改全局状态
4. 考虑使用类作为装饰器,当装饰器需要维护状态时
5. 装饰器链的顺序很重要:靠近函数的装饰器先执行
6. 对于简单场景,优先考虑使用内置装饰器如 @property、@staticmethod
装饰器是 Python 元编程的核心工具之一。掌握高级装饰器技巧,可以写出更简洁、更可维护、更 Pythonic 的代码。在实际项目中,合理使用装饰器可以显著提升代码的可读性和复用性。
