类装饰器的本质是实现了__call__方法的类,Python在遇到@MyDecorator时将被装饰对象传给__init__,再通过__call__执行装饰逻辑。

类装饰器的本质是 __call__ 方法
类能当装饰器用,关键在于它实现了 __call__ 方法。Python 在遇到 @MyDecorator 时,会把被装饰对象(比如一个类)传给 MyDecorator.__init__,然后在后续调用该对象时触发 __call__。这和函数装饰器的执行时机不同:函数装饰器返回一个新函数,而类装饰器通常返回一个新类或原地增强原类。
常见错误是只写了 __init__ 没写 __call__,导致语法报错 TypeError: 'MyDecorator' object is not callable。
- 必须定义
__call__(self, cls),参数cls是被装饰的类 - 返回值通常是修改后的类(可新建子类、动态加属性、或直接 patch 原类)
- 不要在
__init__里做类改造逻辑——那属于初始化配置,真正装饰动作得放在__call__里
给类自动添加字段和方法(如日志、校验)
这是最典型的使用场景:不改原始类定义,却让所有实例自带新行为。比如想让被装饰的类自动拥有 created_at 时间戳和 log_init 方法。
class AutoLog:
def __init__(self, prefix=""):
self.prefix = prefix
<pre class="brush:php;toolbar:false;">def __call__(self, cls):
original_init = cls.__init__
def new_init(self, *args, **kwargs):
import datetime
self.created_at = datetime.datetime.now()
self.log_init = lambda: print(f"{self.prefix} initialized at {self.created_at}")
original_init(self, *args, **kwargs)
cls.__init__ = new_init
return cls@AutoLog(prefix="User:") class User: def init(self, name): self.name = name
注意:这里直接覆写了 cls.__init__,不是返回新类。这样更轻量,但要注意别覆盖掉父类已有逻辑;若原类有继承关系,需用 super().__init__ 显式调用。
立即学习“Python免费学习笔记(深入)”;
- 动态添加的方法(如
log_init)绑定在实例上,不是类方法 - 如果想加类属性或类方法,直接赋值给
cls即可,例如cls.version = "1.0" - 避免在
__call__中做耗时操作——类装饰发生在模块导入时,会影响启动速度
用类装饰器实现单例或缓存实例
类装饰器适合控制类的“构造过程”,比如确保全局只有一个实例(单例),或按参数缓存构造结果(原型模式)。关键点在于拦截 cls(...) 调用,而不是只改定义。
这时候不能只靠改 __init__,得重写 __new__ 或用元类配合;但类装饰器可以优雅地替换整个类的构造逻辑:
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
class Singleton:
def __call__(self, cls):
instances = {}
original_new = cls.__new__
<pre class="brush:php;toolbar:false;"> def new(cls, *args, **kwargs):
key = (cls, args, tuple(sorted(kwargs.items())))
if key not in instances:
# 注意:__new__ 必须返回实例,且不能调用 cls.__new__(cls) 两次
instance = original_new(cls)
instances[key] = instance
# 再触发 __init__(如果用户没禁用)
if hasattr(instance, '__init__') and instance.__init__ is not object.__init__:
instance.__init__(*args, **kwargs)
return instances[key]
cls.__new__ = new
return cls
这个例子容易踩坑:直接在 __new__ 里调用 cls() 会无限递归;必须用 original_new(cls) 绕过装饰后的逻辑。
- 单例键(
key)是否包含args/kwargs取决于你要的是“全局唯一”还是“参数唯一” - 如果被装饰类本身定义了
__new__,要小心兼容——最好检查cls.__new__ is not object.__new__ - 这种模式不适合带可变默认参数的类,因为
args/kwargs的哈希可能失败
装饰器类如何接收参数并保持可复用性
带参数的类装饰器(如 @TrackChanges(fields=["name", "email"]))需要两层封装:外层是接收参数的函数,内层才是真正的装饰器类。但很多人误写成在类的 __init__ 里接装饰目标,导致语法错误。
正确结构是:
def TrackChanges(**options):
def decorator(cls):
# 这里才是真正的装饰逻辑
fields = options.get("fields", [])
original_init = cls.__init__
<pre class="brush:php;toolbar:false;"> def new_init(self, *args, **kwargs):
self._changes = {}
original_init(self, *args, **kwargs)
# 记录初始值(简化版)
for f in fields:
if hasattr(self, f):
self._changes[f] = getattr(self, f)
cls.__init__ = new_init
return cls
return decorator # 返回函数,不是类!用法:
@TrackChanges(fields=["name", "email"]) class Person: def init(self, name, email): self.name = name self.email = email
重点:带参数的装饰器必须是「函数返回装饰器」,哪怕内部用类实现,外层也得是函数。否则 @TrackChanges(...) 会被解析为 TrackChanges(...)(Person),而类实例不可调用。
- 如果你坚持用类实现带参装饰器,得让该类同时支持两种调用方式:
TrackChanges()和TrackChanges()(cls),但极易混淆,不推荐 - 参数校验(如
fields类型检查)应放在外层函数里,而不是decorator函数中 - 调试时打印
print(f"Decorating {cls.__name__} with {options}")很有用,因为类装饰发生在 import 阶段,不易断点
类装饰器真正难的不是语法,而是厘清「什么时候执行」:模块加载时运行 <strong>call</strong>,实例化时才可能触发你注入的 <strong>new</strong> 或 <strong>init</strong> 替换逻辑。混用这两层时机,就容易出现“看似装饰了,但行为没生效”的问题。

















