类装饰器

2 阅读3分钟

类装饰器

类装饰器有两种含义:

  1. 用类来实现装饰器(类实例作为装饰器)
  2. 装饰类的装饰器(装饰器作用于类)

下面分别解释。


一、用类实现装饰器(类实例作为装饰器)

普通的装饰器是函数,但任何可调用对象都可以作为装饰器。如果一个类定义了 __call__ 方法,那么它的实例就可以像函数一样被调用,因此可以用类来编写装饰器。

基本模式

class MyDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        # 调用前增强
        print("调用前")
        result = self.func(*args, **kwargs)
        # 调用后增强
        print("调用后")
        return result

@MyDecorator
def say_hello():
    print("Hello!")

say_hello()

输出

调用前
Hello!
调用后

说明

  • @MyDecorator 等价于 say_hello = MyDecorator(say_hello)
  • 实例化时 __init__ 保存原函数
  • 调用 say_hello() 时触发 __call__ 方法

带参数的类装饰器

如果需要向装饰器传递参数,__init__ 接收参数,__call__ 接收函数。

class Retry:
    def __init__(self, times=3):
        self.times = times

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            for i in range(self.times):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if i == self.times - 1:
                        raise e
                    print(f"重试 {i+1}")
        return wrapper

@Retry(times=5)
def unstable_call():
    raise ValueError("失败")

unstable_call()

说明

  • @Retry(times=5) 先创建实例 Retry(times=5),该实例是可调用对象(因为定义了 __call__
  • 然后实例被调用,传入 unstable_call__call__(func) 返回 wrapper
  • 最终 unstable_call 指向 wrapper

二、装饰类的装饰器(装饰器作用于类)

装饰器不仅可以装饰函数,也可以装饰类。此时装饰器接收一个类,返回一个新的类(或修改后的类)。

示例:为类添加属性

def add_attr(cls):
    cls.new_attr = "added by decorator"
    return cls

@add_attr
class MyClass:
    pass

print(MyClass.new_attr)   # 输出: added by decorator

示例:单例模式(类装饰器)

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("初始化数据库")

db1 = Database()   # 打印一次
db2 = Database()   # 不再打印,返回同一个实例
print(db1 is db2)  # True

注意:上面的 singleton 装饰器返回的是一个函数,而不是类。严格来说它把类变成了函数。如果希望返回一个真正的类,可以定义一个内部类并返回。


三、类装饰器 vs 函数装饰器

特点函数装饰器类装饰器(用类实现)
实现方式def decorator(func):class Decorator: __init__ + __call__
状态保持需要借助 nonlocal 或可变对象可以用实例属性自然保存状态
可读性简洁适合复杂配置、多个方法
带参数三层嵌套__init__ 接收参数,__call__ 接收函数

选择建议

  • 简单逻辑 → 函数装饰器
  • 需要保存状态、提供额外方法、或逻辑复杂 → 类装饰器

四、综合示例:带状态统计的类装饰器

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"调用次数: {self.count}")
        return self.func(*args, **kwargs)

@CountCalls
def hello():
    print("Hello")

hello()  # 调用次数: 1 \n Hello
hello()  # 调用次数: 2 \n Hello

类装饰器可以方便地存储每个被装饰函数的调用计数,而无需使用 nonlocal 或函数属性。


总结

  • 类装饰器通常指用类来实现装饰器(利用 __call__),它比函数装饰器更适合需要状态保持的场景。
  • 另外还有装饰类的装饰器,即装饰器作用于类,可以修改类或将其转换为其他可调用对象。
  • 两者名称相似,注意上下文区分。