类装饰器
类装饰器有两种含义:
- 用类来实现装饰器(类实例作为装饰器)
- 装饰类的装饰器(装饰器作用于类)
下面分别解释。
一、用类实现装饰器(类实例作为装饰器)
普通的装饰器是函数,但任何可调用对象都可以作为装饰器。如果一个类定义了 __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__),它比函数装饰器更适合需要状态保持的场景。 - 另外还有装饰类的装饰器,即装饰器作用于类,可以修改类或将其转换为其他可调用对象。
- 两者名称相似,注意上下文区分。