Python中的类装饰器与装饰器类详解
字数 742 2025-12-06 16:58:50
Python中的类装饰器与装饰器类详解
我们先明确一个容易混淆的概念:在Python中,"类装饰器"可以指两种不同的东西:
- 用类实现的装饰器(装饰器类)
- 装饰类的装饰器(类装饰器)
今天我们先讲第一种:用类实现的装饰器,然后讲第二种:装饰类的装饰器。
一、用类实现的装饰器(装饰器类)
1. 基本概念
装饰器类是一个实现了__init__和__call__方法的类,它通过类实例来装饰函数。这种方式的优势是可以用状态来记录信息。
2. 实现原理
class Timer:
"""用类实现的计时装饰器"""
def __init__(self, func):
self.func = func
self.times = [] # 可以保存多次调用的时间
def __call__(self, *args, **kwargs):
import time
start = time.time()
result = self.func(*args, **kwargs)
elapsed = time.time() - start
self.times.append(elapsed)
print(f"{self.func.__name__} 执行时间: {elapsed:.6f}秒")
return result
3. 使用示例
@Timer
def slow_function():
import time
time.sleep(0.1)
return "完成"
# 调用
result = slow_function() # 输出: slow_function 执行时间: 0.100123秒
print(result) # 输出: 完成
# 再次调用
slow_function() # 输出: slow_function 执行时间: 0.100456秒
# 可以访问装饰器的状态
print(f"总调用次数: {len(slow_function.times)}") # 输出: 2
print(f"总耗时: {sum(slow_function.times):.6f}秒")
4. 带参数的装饰器类
class Retry:
"""带重试功能的装饰器类"""
def __init__(self, max_retries=3, delay=1):
self.max_retries = max_retries
self.delay = delay
def __call__(self, func):
def wrapper(*args, **kwargs):
import time
for attempt in range(self.max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == self.max_retries - 1:
raise
print(f"调用失败 ({e}),{self.delay}秒后重试...")
time.sleep(self.delay)
return None
return wrapper
@Retry(max_retries=3, delay=2)
def risky_function():
import random
if random.random() < 0.7:
raise ValueError("随机失败")
return "成功"
二、装饰类的装饰器(类装饰器)
1. 基本概念
这是接收一个类作为参数,返回一个新类或修改后类的装饰器。可以用于:
- 添加/修改类的属性或方法
- 实现注册模式
- 自动添加特殊方法
- 实现单例模式
2. 简单示例:给类添加日志
def add_logging(cls):
"""给类的所有方法添加日志的装饰器"""
original_init = cls.__init__
def new_init(self, *args, **kwargs):
print(f"[LOG] 创建 {cls.__name__} 实例")
original_init(self, *args, **kwargs)
cls.__init__ = new_init
# 给所有方法添加日志
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if callable(attr) and not attr_name.startswith("__"):
def make_wrapper(method):
def wrapper(self, *args, **kwargs):
print(f"[LOG] 调用 {cls.__name__}.{method.__name__}")
return method(self, *args, **kwargs)
return wrapper
setattr(cls, attr_name, make_wrapper(attr))
return cls
@add_logging
class MyClass:
def __init__(self, value):
self.value = value
def process(self):
return self.value * 2
# 使用
obj = MyClass(10) # 输出: [LOG] 创建 MyClass 实例
result = obj.process() # 输出: [LOG] 调用 MyClass.process
print(result) # 输出: 20
3. 类装饰器实现单例模式
def singleton(cls):
"""单例装饰器"""
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self):
print("Database 初始化")
self.connection = "连接对象"
# 测试
db1 = Database() # 输出: Database 初始化
db2 = Database() # 无输出,使用缓存
print(db1 is db2) # 输出: True
4. 类注册器模式
class PluginRegistry:
"""插件注册器"""
plugins = {}
@classmethod
def register(cls, name):
def decorator(plugin_cls):
cls.plugins[name] = plugin_cls
return plugin_cls
return decorator
@classmethod
def get_plugin(cls, name):
return cls.plugins.get(name)
# 使用
@PluginRegistry.register("excel")
class ExcelProcessor:
def process(self, data):
return f"处理Excel: {data}"
@PluginRegistry.register("csv")
class CSVProcessor:
def process(self, data):
return f"处理CSV: {data}"
# 获取插件
processor = PluginRegistry.get_plugin("excel")()
print(processor.process("数据")) # 输出: 处理Excel: 数据
三、组合使用:装饰器类作为类装饰器
1. 用类实现的类装饰器
class AddMethod:
"""给类添加方法的装饰器类"""
def __init__(self, method_name):
self.method_name = method_name
def __call__(self, method_func):
def class_decorator(cls):
setattr(cls, self.method_name, method_func)
return cls
return class_decorator
def to_dict(self):
"""要添加的方法"""
return {k: v for k, v in self.__dict__.items()
if not k.startswith('_')}
# 使用
@AddMethod("to_dict")(to_dict)
class User:
def __init__(self, name, age):
self.name = name
self.age = age
self._private = "私有"
# 测试
user = User("Alice", 25)
print(user.to_dict()) # 输出: {'name': 'Alice', 'age': 25}
2. 更优雅的写法
def add_method(method_name):
"""工厂函数,返回类装饰器"""
def decorator(method_func):
def class_decorator(cls):
setattr(cls, method_name, method_func)
return cls
return class_decorator
return decorator
@add_method("to_json")(lambda self: f'{{"name": "{self.name}"}}')
@add_method("display")(lambda self: f"用户: {self.name}")
class Person:
def __init__(self, name):
self.name = name
# 测试
p = Person("Bob")
print(p.display()) # 输出: 用户: Bob
print(p.to_json()) # 输出: {"name": "Bob"}
四、实际应用场景
1. Django模型注册
# 类似Django的admin注册
class ModelAdminRegistry:
models = {}
@classmethod
def register(cls, model):
def decorator(admin_class):
cls.models[model] = admin_class
return admin_class
return decorator
@ModelAdminRegistry.register(User)
class UserAdmin:
list_display = ['name', 'age']
2. 数据验证装饰器
def validate_fields(**validators):
"""验证类属性的装饰器"""
def class_decorator(cls):
original_init = cls.__init__
def new_init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
for field, validator in validators.items():
if hasattr(self, field):
value = getattr(self, field)
if not validator(value):
raise ValueError(f"字段 {field} 验证失败: {value}")
cls.__init__ = new_init
return cls
return class_decorator
def is_positive(x):
return x > 0
def is_non_empty_string(s):
return isinstance(s, str) and len(s) > 0
@validate_fields(
age=is_positive,
name=is_non_empty_string
)
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# 测试
try:
s1 = Student("", 20) # 会抛出异常
except ValueError as e:
print(e) # 输出: 字段 name 验证失败:
s2 = Student("Alice", 20) # 正常
s3 = Student("Bob", -5) # 会抛出异常
五、注意事项与最佳实践
1. 保持类装饰器的透明性
def preserve_metadata(cls):
"""保持元数据的类装饰器"""
import functools
for name, method in cls.__dict__.items():
if callable(method):
setattr(cls, name, functools.wraps(method)(method))
return cls
2. 处理继承
def add_mixin(mixin_class):
"""添加Mixin的类装饰器"""
def decorator(cls):
# 动态创建新类,继承自cls和mixin_class
return type(
cls.__name__,
(cls, mixin_class),
{}
)
return decorator
class JSONMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
@add_mixin(JSONMixin)
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
# 测试
p = Product("Book", 29.99)
print(p.to_json()) # 输出: {"name": "Book", "price": 29.99}
3. 性能考虑
- 类装饰器在导入时执行,只运行一次
- 避免在类装饰器中执行耗时的操作
- 考虑使用缓存机制
总结
- 装饰器类:用类实现的装饰器,通过
__init__接收函数,__call__包装函数 - 类装饰器:装饰类的装饰器,接收类作为参数,返回修改后的类
- 核心区别:
- 装饰器类:装饰函数/方法
- 类装饰器:装饰类本身
- 应用场景:
- 单例模式实现
- 插件/注册器模式
- 自动添加方法/属性
- 数据验证
- AOP(面向切面编程)
这种灵活性使得Python的装饰器系统非常强大,但也要注意合理使用,避免过度设计。