Python中的类装饰器与装饰器类详解
字数 742 2025-12-06 16:58:50

Python中的类装饰器与装饰器类详解

我们先明确一个容易混淆的概念:在Python中,"类装饰器"可以指两种不同的东西:

  1. 用类实现的装饰器(装饰器类)
  2. 装饰类的装饰器(类装饰器)

今天我们先讲第一种:用类实现的装饰器,然后讲第二种:装饰类的装饰器。


一、用类实现的装饰器(装饰器类)

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. 性能考虑

  • 类装饰器在导入时执行,只运行一次
  • 避免在类装饰器中执行耗时的操作
  • 考虑使用缓存机制

总结

  1. 装饰器类:用类实现的装饰器,通过__init__接收函数,__call__包装函数
  2. 类装饰器:装饰类的装饰器,接收类作为参数,返回修改后的类
  3. 核心区别
    • 装饰器类:装饰函数/方法
    • 类装饰器:装饰类本身
  4. 应用场景
    • 单例模式实现
    • 插件/注册器模式
    • 自动添加方法/属性
    • 数据验证
    • AOP(面向切面编程)

这种灵活性使得Python的装饰器系统非常强大,但也要注意合理使用,避免过度设计。

Python中的类装饰器与装饰器类详解 我们先明确一个容易混淆的概念:在Python中,"类装饰器"可以指两种不同的东西: 用类实现的装饰器(装饰器类) 装饰类的装饰器(类装饰器) 今天我们先讲第一种:用类实现的装饰器,然后讲第二种:装饰类的装饰器。 一、用类实现的装饰器(装饰器类) 1. 基本概念 装饰器类是一个实现了 __init__ 和 __call__ 方法的类,它通过类实例来装饰函数。这种方式的优势是可以用状态来记录信息。 2. 实现原理 3. 使用示例 4. 带参数的装饰器类 二、装饰类的装饰器(类装饰器) 1. 基本概念 这是接收一个类作为参数,返回一个新类或修改后类的装饰器。可以用于: 添加/修改类的属性或方法 实现注册模式 自动添加特殊方法 实现单例模式 2. 简单示例:给类添加日志 3. 类装饰器实现单例模式 4. 类注册器模式 三、组合使用:装饰器类作为类装饰器 1. 用类实现的类装饰器 2. 更优雅的写法 四、实际应用场景 1. Django模型注册 2. 数据验证装饰器 五、注意事项与最佳实践 1. 保持类装饰器的透明性 2. 处理继承 3. 性能考虑 类装饰器在导入时执行,只运行一次 避免在类装饰器中执行耗时的操作 考虑使用缓存机制 总结 装饰器类 :用类实现的装饰器,通过 __init__ 接收函数, __call__ 包装函数 类装饰器 :装饰类的装饰器,接收类作为参数,返回修改后的类 核心区别 : 装饰器类:装饰函数/方法 类装饰器:装饰类本身 应用场景 : 单例模式实现 插件/注册器模式 自动添加方法/属性 数据验证 AOP(面向切面编程) 这种灵活性使得Python的装饰器系统非常强大,但也要注意合理使用,避免过度设计。