Python 的类装饰器是什么?和元类、函数装饰器有什么区别?
简化版
类装饰器(class decorator)是「作用在『类』上的装饰器」——它接收一个类作为参数,对这个类做修改/增强(加方法、加属性、注册、包装),然后返回这个类(或一个新类)。语法和函数装饰器一样用 @,只是装饰的目标是类:@register class MyClass: ... 等价于 MyClass = register(MyClass)。两层含义:① 「装饰器是个函数/类,作用于类」——def add_repr(cls): cls.__repr__ = ...; return cls(给类加个 __repr__);② 和「函数装饰器」的区别只是目标不同(一个装饰函数、一个装饰类,机制一样)。典型用途:给类批量加方法/属性(@dataclass 就是类装饰器,自动生成 __init__/__repr__/__eq__)、注册类到某个表(插件系统 @register)、包装类(加单例、加日志、验证)、修改类的行为。和元类(metaclass)的区别:类装饰器在「类创建之后」对类做处理(简单、够用大多数场景);元类在「类创建的过程中」介入(更底层、更强大但更复杂、能影响子类)——「能用类装饰器就别用元类」。核心记忆:类装饰器接收类、修改/增强后返回类(@dataclass/@register);比函数装饰器只是目标是类;比元类简单(类创建后处理,不影响子类),能用装饰器就别用元类。
详细版
类装饰器 vs 函数装饰器 vs 元类:
| 维度 | 函数装饰器 | 类装饰器 | 元类 |
|---|---|---|---|
| 作用对象 | 函数 | 类 | 类的创建过程 |
| 时机 | 函数定义后 | 类定义后 | 类创建中 |
| 影响子类 | — | 不影响 | 影响(子类也走元类) |
| 复杂度 | 低 | 低 | 高 |
# 类装饰器:接收类、修改后返回
def add_repr(cls):
def __repr__(self):
return f"{cls.__name__}({self.__dict__})"
cls.__repr__ = __repr__ # 给类加方法
return cls # 返回类
@add_repr
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
p = Point(1, 2)
print(p) # Point({'x': 1, 'y': 2})(用装饰器加的 __repr__)
# 等价:Point = add_repr(Point)
# 注册模式(插件系统)
registry = {}
def register(cls):
registry[cls.__name__] = cls # 注册到全局表
return cls
@register
class PluginA: ...
@register
class PluginB: ...
print(registry) # {'PluginA': ..., 'PluginB': ...}
# 带参数的类装饰器
def register_as(name):
def decorator(cls):
registry[name] = cls
return cls
return decorator
@register_as("my_plugin")
class MyPlugin: ...
# 单例装饰器(返回包装后的行为)
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: ...
db1 = Database()
db2 = Database()
print(db1 is db2) # True(单例)
# @dataclass 就是类装饰器(自动生成方法)
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
# 自动生成 __init__/__repr__/__eq__
# 类装饰器(用类实现,而非函数)
class CountInstances:
def __init__(self, cls):
self.cls = cls
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
return self.cls(*args, **kwargs)
⚠️ 核心认知:类装饰器和函数装饰器本质是同一回事——都是「
@decorator把下面的目标(函数或类)传给 decorator、用返回值替换原目标」;@add_repr class Point就是Point = add_repr(Point);区别只是「装饰的目标是类而非函数」。类装饰器有两种常见「返回」方式:① 返回原类(修改后)——最常见,def deco(cls): cls.新方法 = ...; return cls(给类加方法/属性、注册、然后返回同一个类,类的身份不变);② 返回一个替换品——如单例装饰器返回一个「拦截实例化的函数/对象」(这时Database名字指向的其实不是原类了,用isinstance等要小心)。类装饰器和元类都能「定制类」,但定位不同:类装饰器在「类已经创建好之后」对它做后处理(拿到成品类,改改它),简单直接、够用绝大多数场景(加方法、注册、包装);元类在「类被创建的那一刻」介入(控制类是怎么造出来的),更底层、更强大(能修改类体的解析、能让所有子类自动应用),但也更复杂、更难懂。Python 社区的共识是「如果类装饰器能做到,就别用元类」——@dataclass用装饰器而不是元类就是例证。类装饰器的一个局限是「不自动作用于子类」(装饰 A 不会影响 A 的子类 B),需要子类也装饰或改用元类/__init_subclass__。
完整版教学
一、类装饰器是什么
先理解类装饰器的本质:
类装饰器:作用在"类"上的装饰器
接收一个类,处理后返回一个类(或替换品)
语法(和函数装饰器一样):
@decorator
class MyClass: ...
等价于:
class MyClass: ...
MyClass = decorator(MyClass)
→ @ 只是语法糖
装饰器做什么:
decorator 接收 cls(类对象)
→ 可以:加方法/属性、注册、包装、修改
→ 返回 cls(或新的东西)替换原类名
最简单的例子:
def add_hello(cls):
cls.hello = lambda self: "hi" # 给类加方法
return cls # 返回类
@add_hello
class C: ...
C().hello() # "hi"
类是对象(所以能被装饰):
class 定义会创建一个"类对象"(type 的实例)
→ 类对象可以被传递、修改(加属性、加方法)
→ 装饰器就是拿到这个类对象来处理
返回值的两种情况:
① 返回原类(修改后):类身份不变(最常见)
② 返回替换品(新类/函数/对象):类名指向别的东西
所以类装饰器接收类、处理后返回类;@ 是语法糖(MyClass=decorator(MyClass));类是对象可被改
类装饰器:作用在「类」上的装饰器——接收一个类、处理后返回一个类(或替换品)。语法(和函数装饰器一样):@decorator class MyClass: ... 等价于 class MyClass: ...; MyClass = decorator(MyClass)(@ 只是语法糖)。装饰器做什么:decorator 接收 cls(类对象)→ 可以加方法/属性、注册、包装、修改 → 返回 cls(或新的东西)替换原类名。最简单的例子:def add_hello(cls): cls.hello = lambda self: "hi"; return cls。类是对象(所以能被装饰):class 定义会创建一个「类对象」(type 的实例)、类对象可以被传递/修改、装饰器就是拿到这个类对象来处理。返回值的两种情况:① 返回原类(修改后、类身份不变、最常见)、② 返回替换品(新类/函数/对象、类名指向别的东西)。理解「类装饰器接收类、处理后返回类;@ 是语法糖(MyClass=decorator(MyClass));类是对象(type 的实例)可被修改;返回原类(修改后)或替换品」,就理解了类装饰器。
二、常见用途
理解类装饰器的典型应用:
① 给类批量加方法/属性:
def add_methods(cls):
cls.__repr__ = lambda self: f"{cls.__name__}(...)"
cls.to_dict = lambda self: self.__dict__
return cls
→ @dataclass 就是这类(自动生成 __init__/__repr__/__eq__)
② 注册(插件系统/工厂):
registry = {}
def register(cls):
registry[cls.__name__] = cls
return cls
@register
class Handler: ...
→ 类被定义时自动注册到表(无需手动登记)
③ 包装/增强类的行为:
单例、缓存、日志、访问控制
def singleton(cls):
instances = {}
def wrapper(*a, **k):
if cls not in instances:
instances[cls] = cls(*a, **k)
return instances[cls]
return wrapper # 拦截实例化
④ 验证/约束类:
def require_methods(*names):
def deco(cls):
for n in names:
if not hasattr(cls, n):
raise TypeError(f"缺少 {n}")
return cls
return deco
@require_methods("save", "load")
class Model: ... # 检查类有没有 save/load
⑤ 修改类属性/配置:
给类设置默认配置、修改类变量等
带参数的类装饰器:
@register_as("name") # 装饰器工厂:先接收参数、返回真装饰器
→ def register_as(name): def deco(cls): ...; return deco
所以用途:批量加方法(@dataclass)/注册(插件)/包装(单例)/验证/改配置
类装饰器的典型应用:① 给类批量加方法/属性(@dataclass 自动生成 __init__/__repr__/__eq__);② 注册(插件系统/工厂)(registry[cls.__name__] = cls、类被定义时自动注册无需手动登记);③ 包装/增强类的行为(单例、缓存、日志、访问控制,singleton 拦截实例化);④ 验证/约束类(require_methods 检查类有没有某些方法);⑤ 修改类属性/配置。带参数的类装饰器:@register_as("name")(装饰器工厂:先接收参数、返回真装饰器,def register_as(name): def deco(cls): ...; return deco)。理解「用途:①批量加方法(@dataclass)②注册(插件系统 registry[name]=cls)③包装(单例/缓存/日志)④验证(检查有没有某方法)⑤改配置;带参数用装饰器工厂(接收参数返回真装饰器)」,就掌握了常见用途。
三、类装饰器 vs 函数装饰器
理解两者的异同:
本质相同:都是 @ 语法糖,装饰目标不同
函数装饰器:
@deco
def f(): ...
→ f = deco(f)(装饰函数)
→ 通常返回一个"包装函数"(wrapper)
类装饰器:
@deco
class C: ...
→ C = deco(C)(装饰类)
→ 通常返回"修改后的类"(或替换品)
区别1:目标不同(函数 vs 类)
区别2:常见返回方式不同
函数装饰器:常返回 wrapper(包装原函数,加前后逻辑)
类装饰器:常返回原类本身(修改后,加方法/注册)
少数返回替换品(如单例返回拦截函数)
区别3:函数装饰器要保留元数据(wraps)
函数装饰器返回 wrapper → 用 @functools.wraps 保留原函数名/文档
类装饰器返回原类 → 类身份不变,一般不需要 wraps
(若返回新类/函数,才需要处理元数据)
共同点:
① 都用 @ 语法(语法糖)
② 都可带参数(装饰器工厂)
③ 都能用函数或类实现装饰器
④ 都能堆叠(多个装饰器)
堆叠顺序(从下往上应用):
@a
@b
class C: ...
→ C = a(b(C))(先 b 后 a)
所以类装饰器和函数装饰器本质相同(@语法糖),只是装饰目标是类(常返回修改后的类)
本质相同:都是 @ 语法糖、装饰目标不同。函数装饰器:@deco def f() → f = deco(f)(装饰函数、通常返回包装函数 wrapper)。类装饰器:@deco class C → C = deco(C)(装饰类、通常返回修改后的类或替换品)。区别1:目标不同(函数 vs 类)。区别2:常见返回方式不同(函数装饰器常返回 wrapper 包装原函数、类装饰器常返回原类本身修改后、少数返回替换品如单例)。区别3:函数装饰器要保留元数据(wraps)(函数装饰器返回 wrapper 用 @functools.wraps 保留原函数名/文档、类装饰器返回原类身份不变一般不需要 wraps)。共同点:都用 @ 语法、都可带参数(装饰器工厂)、都能用函数或类实现、都能堆叠。堆叠顺序(从下往上应用):@a @b class C → C = a(b(C))(先 b 后 a)。理解「类装饰器和函数装饰器本质相同(@语法糖);目标不同(函数 vs 类);类装饰器常返回修改后的原类、函数装饰器常返回 wrapper;函数装饰器要 wraps 保留元数据、类装饰器返回原类一般不需要;都可带参数堆叠」,就掌握了两者异同。
四、类装饰器 vs 元类
理解类装饰器和元类的区别与选择:
两者都能"定制类",但介入时机和能力不同:
类装饰器:类创建"之后"的后处理
class C: ... # 类先正常创建
C = decorator(C) # 然后装饰器加工成品类
→ 拿到已经造好的类,改改它(加方法、注册)
→ 简单、直接、易懂
元类:类创建"之中"的介入
class C(metaclass=Meta): ...
→ Meta.__new__/__init__ 控制"类是怎么被造出来的"
→ 能修改类体解析、控制继承、影响所有子类
→ 底层、强大、复杂
关键区别1:影响子类
类装饰器:只装饰"被装饰的那个类",不影响子类
@deco class A: ...
class B(A): ... # B 不会自动被 deco 装饰!
元类:子类自动继承元类
class A(metaclass=Meta): ...
class B(A): ... # B 也走 Meta(自动应用)
关键区别2:能力
类装饰器:能做"后处理"(加方法、注册、包装)
元类:能做"创建时控制"(拦截类体、验证类定义、
自动注册所有子类、修改 MRO 等更底层的事)
选择原则:"能用类装饰器就别用元类"
① 加方法/属性、注册、包装 → 类装饰器(简单)
② 要影响所有子类、控制类创建过程 → 元类(或 __init_subclass__)
③ __init_subclass__(3.6+)是"轻量元类"替代
(子类创建时的钩子,比元类简单、能影响子类)
@dataclass 的选择:用装饰器(不用元类)
→ 因为只需"给类加方法",装饰器够了、更简单
所以类装饰器=类创建后处理(简单、不影响子类)、元类=类创建中介入(强大、影响子类);能用装饰器就别用元类
两者都能「定制类」,但介入时机和能力不同:类装饰器:类创建「之后」的后处理(类先正常创建、然后装饰器加工成品类、拿到已造好的类改改它、简单直接易懂);元类:类创建「之中」的介入(class C(metaclass=Meta)、Meta.__new__/__init__ 控制类是怎么被造出来的、能修改类体解析/控制继承/影响所有子类、底层强大复杂)。关键区别1:影响子类——类装饰器只装饰被装饰的那个类不影响子类(@deco class A 后 class B(A) 不会自动被 deco 装饰)、元类子类自动继承元类(class B(A) 也走 Meta)。关键区别2:能力——类装饰器做后处理(加方法/注册/包装)、元类做创建时控制(拦截类体/验证类定义/自动注册所有子类/修改 MRO)。选择原则:「能用类装饰器就别用元类」——加方法/注册/包装用类装饰器(简单)、要影响所有子类/控制类创建过程用元类(或 __init_subclass__)、__init_subclass__(3.6+)是「轻量元类」替代。理解「类装饰器=类创建后处理(简单、不影响子类)、元类=类创建中介入(强大、影响所有子类);类装饰器不影响子类(@deco A 不影响 B(A))、元类影响子类;能用类装饰器就别用元类;@dataclass 用装饰器」,就掌握了类装饰器 vs 元类。
五、注意事项与陷阱
理解类装饰器的坑:
陷阱1:返回替换品时,类身份变了
@singleton # 返回的是拦截函数,不是原类
class DB: ...
print(type(DB)) # <class 'function'>(不是 type!)
isinstance(db, DB) # 可能出问题(DB 不是类了)
→ 返回替换品时,isinstance/issubclass/类型检查要小心
→ 尽量返回原类(修改后),保持类身份
陷阱2:不影响子类
@add_repr class Base: ...
class Sub(Base): ... # Sub 继承了 __repr__(因为是加在 Base 上)
→ 但如果装饰器返回"新类",子类继承的是新类还是原类要注意
→ 加方法到 cls 上(return cls)→ 子类能继承(正常)
→ 返回包装(wrapper)→ 子类继承 wrapper 可能出问题
陷阱3:装饰器顺序(堆叠)
@a @b class C → C = a(b(C))
→ 顺序影响结果(尤其都修改类时)
陷阱4:带参数的装饰器多一层
@register_as("x") 是"装饰器工厂"
def register_as(name): # 接收参数
def deco(cls): # 真装饰器
...
return cls
return deco
→ 别忘了工厂返回装饰器、装饰器返回类
最佳实践:
① 优先返回原类(修改后),保持类身份
② 加方法/属性直接改 cls(return cls)
③ 需要影响子类 → 用元类或 __init_subclass__
④ 带参数用装饰器工厂
⑤ 能用装饰器就别用元类(简单优先)
所以陷阱:返回替换品类身份变(isinstance 出问题)、不影响子类、堆叠顺序、带参多一层
类装饰器的坑:陷阱1:返回替换品时,类身份变了(@singleton 返回拦截函数不是原类、type(DB) 是 function 不是 type、isinstance/类型检查要小心、尽量返回原类保持身份);陷阱2:不影响子类(加方法到 cls 上 return cls 子类能继承正常、返回包装 wrapper 子类继承可能出问题);陷阱3:装饰器顺序(堆叠)(@a @b class C → C = a(b(C))、顺序影响结果);陷阱4:带参数的装饰器多一层(@register_as("x") 是装饰器工厂:接收参数返回真装饰器、真装饰器返回类)。最佳实践:① 优先返回原类(修改后)保持身份、② 加方法/属性直接改 cls(return cls)、③ 需要影响子类用元类或 __init_subclass__、④ 带参数用装饰器工厂、⑤ 能用装饰器就别用元类。理解「陷阱:①返回替换品类身份变(type 变 function、isinstance 出问题、尽量返回原类)②不影响子类③堆叠顺序 a(b(C))④带参数多一层(工厂返回装饰器);最佳实践返回原类+直接改 cls+影响子类用元类/init_subclass」,就掌握了注意事项。
六、总结与实践
总结类装饰器:
核心:
类装饰器 = 作用在类上的装饰器
接收类、处理(加方法/注册/包装)、返回类
@deco class C → C = deco(C)
常见用途:
批量加方法(@dataclass)、注册(插件)、
包装(单例/缓存)、验证、改配置
vs 函数装饰器:
本质相同(@ 语法糖)、只是目标是类
类装饰器常返回修改后的原类(不太需要 wraps)
vs 元类:
类装饰器:类创建后处理(简单、不影响子类)
元类:类创建中介入(强大、影响所有子类)
→ 能用类装饰器就别用元类
带参数:装饰器工厂(接收参数 → 返回装饰器 → 返回类)
关键陷阱:
① 返回替换品 → 类身份变(isinstance 出问题)
② 不自动影响子类(要影响用元类/__init_subclass__)
实践建议:
① 加方法/注册/包装 → 类装饰器(优先返回原类)
② 影响所有子类 → __init_subclass__ 或元类
③ 简单优先(能装饰器就别元类)
核心总结:
类装饰器接收类、修改后返回类(@dataclass/@register)
比函数装饰器只是目标是类
比元类简单(创建后处理、不影响子类)
所以类装饰器接收类、修改返回类,比函数装饰器目标是类,比元类简单(不影响子类)
核心:类装饰器 = 作用在类上的装饰器(接收类、处理加方法/注册/包装、返回类、@deco class C → C = deco(C))。常见用途:批量加方法(@dataclass)、注册(插件)、包装(单例/缓存)、验证、改配置。vs 函数装饰器:本质相同(@ 语法糖)、只是目标是类(类装饰器常返回修改后的原类、不太需要 wraps)。vs 元类:类装饰器类创建后处理(简单、不影响子类)、元类类创建中介入(强大、影响所有子类)——能用类装饰器就别用元类。带参数:装饰器工厂。关键陷阱:返回替换品类身份变、不自动影响子类。理解「类装饰器接收类、修改后返回类(@dataclass/@register);比函数装饰器只是目标是类;比元类简单(创建后处理、不影响子类);带参数用工厂;陷阱返回替换品身份变、不影响子类;能装饰器就别元类」,就掌握了总结与实践。
记忆钩子:「类装饰器(class decorator)是作用在『类』上的装饰器——接收一个类作为参数,对它做修改/增强(加方法、加属性、注册、包装),然后返回这个类(或替换品);语法和函数装饰器一样用@:@register class MyClass 等价于 MyClass=register(MyClass);★两种返回方式:①返回原类(修改后)——最常见,def deco(cls):cls.新方法=…;return cls(给类加方法/属性/注册,类身份不变)②返回替换品——如单例装饰器返回拦截实例化的函数(这时类身份变了、type(DB)变成 function、isinstance 要小心);★典型用途:给类批量加方法(@dataclass 就是类装饰器,自动生成__init__/repr/eq)、注册到表(插件系统@register)、包装(单例/缓存/日志)、验证类;★和函数装饰器区别只是目标不同(一个装饰函数常返回 wrapper 要用 functools.wraps、一个装饰类常返回修改后的原类不太需要 wraps),机制一样;★和元类(metaclass)的区别:类装饰器在『类创建之后』对成品类做后处理(简单、够用大多数场景、但★不影响子类——@deco class A 不会自动装饰 A 的子类 B),元类在『类创建的过程中』介入(更底层强大、能影响所有子类但更复杂);共识『能用类装饰器就别用元类』(@dataclass 用装饰器);要影响子类用__init_subclass__(3.6+,轻量元类替代)或元类;带参数用装饰器工厂」。
七、常见误区与追问
- 误区:类装饰器和函数装饰器是完全不同的机制。 本质相同——都是
@decorator语法糖:@deco class C: ...就是C = deco(C)、@deco def f(): ...就是f = deco(f);区别只在「装饰的目标是类还是函数」;机制、语法、带参数(装饰器工厂)、堆叠都一样;唯一实践差异是「常见的返回方式」——函数装饰器常返回一个包装函数(要用 functools.wraps 保留原函数元数据),类装饰器常返回修改后的原类(类身份不变、一般不需要 wraps)。 - 误区:类装饰器一定要返回原来那个类。 不一定——常见的是返回原类(修改后,加了方法/属性/注册的同一个类,身份不变,最推荐),但也可以返回一个「替换品」(新类、函数、或包装对象);比如单例装饰器
singleton通常返回一个「拦截实例化的函数」,这时被装饰的名字(如 Database)实际指向的不再是原类而是那个函数、type(Database)会是 function 而非 type、isinstance检查也会受影响;所以「返回替换品」要小心类型检查,尽量优先「返回修改后的原类」保持身份。 - 误区:给一个类加了类装饰器,它的子类也会自动被装饰。 不会——类装饰器只作用于「被它直接装饰的那个类」,不会自动应用到子类:
@deco class A: ...之后class B(A): ...,B 不会被 deco 装饰(除非 B 也显式加 @deco);不过如果装饰器是「往类上加方法/属性并 return cls」,那么子类会通过正常的继承机制继承到这些方法(因为它们加在了父类 A 上);但装饰器本身的「处理逻辑」(如注册、验证)只对 A 执行一次、不会对 B 执行;如果需要「所有子类都自动应用某处理」,要用元类或__init_subclass__。 - 误区:要定制类的创建就必须用元类。 不一定——很多「定制类」的需求(给类加方法/属性、注册类、包装类的实例化、验证类定义)用「类装饰器」就能优雅完成,且比元类简单得多;元类是「类创建过程中介入」的重武器,主要用于「需要影响所有子类、控制类体解析、深度定制类创建」的场景;Python 社区的共识是「能用类装饰器(或
__init_subclass__)就别用元类」;@dataclass就是用类装饰器而非元类实现的典范;只有当类装饰器和__init_subclass__都做不到时才考虑元类。 - 追问:类装饰器和元类都能定制类,它们有什么区别,怎么选? 主要区别在「介入时机」和「是否影响子类」:① 类装饰器在「类已经创建好之后」对成品类做后处理——你拿到一个造好的类对象、给它加方法/属性、注册它、或包装它,简单直接、易懂,但它只作用于被装饰的那个类、不会自动影响子类;② 元类在「类被创建的那一刻」介入——通过
metaclass=Meta,元类的__new__/__init__控制「类是怎么被造出来的」,能修改类体的解析、验证类定义、修改 MRO,而且子类会自动继承元类(所有子类都走同样的元类逻辑),更底层、更强大但也更复杂难懂;选择原则是「能用类装饰器就别用元类」(简单优先):加方法/注册/包装/验证用类装饰器;需要「影响所有子类、控制类创建过程」时,优先考虑__init_subclass__(3.6+ 引入的子类创建钩子,是轻量的元类替代、能影响子类但比元类简单),实在不够再用元类;@dataclass选择用装饰器就是因为「只需给类加方法」、装饰器足够。 - 追问:怎么写一个带参数的类装饰器? 用「装饰器工厂」——多套一层函数:最外层函数接收装饰器的参数、返回真正的装饰器(真正的装饰器接收类、处理后返回类);比如实现
@register_as("my_plugin") class P: ...:def register_as(name):(接收参数 name)def decorator(cls):(真装饰器、接收类)registry[name] = cls(用参数 name)return cls(返回类)return decorator(工厂返回装饰器);调用时@register_as("my_plugin")先执行register_as("my_plugin")得到 decorator、再用 decorator 装饰类 P(即P = register_as("my_plugin")(P));关键是理解「带参数 = 三层:工厂(参数) → 装饰器(类) → 返回类」,比无参数的类装饰器多一层。 - 追问:类装饰器有哪些实际应用场景? 常见的有:① 自动生成样板方法——
@dataclass根据类的字段注解自动生成__init__、__repr__、__eq__(甚至__lt__、__hash__),省去手写;@functools.total_ordering补全比较方法;② 注册到表——插件系统/工厂模式里@register让类被定义时自动登记到全局注册表(registry[cls.__name__] = cls),无需手动维护映射;③ 包装类的行为——@singleton(单例,拦截实例化保证只有一个实例)、缓存实例、给所有实例方法加日志或访问控制;④ 验证/约束类定义——检查类是否实现了某些必需的方法/属性,不满足就在定义时报错(提前发现);⑤ 给类批量设置默认配置、修改类变量;这些场景的共同点是「拿到一个类、对它做统一的加工/登记/包装」,用类装饰器比手写、比元类都更简洁清晰。
八、加强记忆
类装饰器(class decorator)是作用在「类」上的装饰器——接收一个类作为参数、对它做修改/增强(加方法、加属性、注册、包装),然后返回这个类(或替换品);语法和函数装饰器一样用 @:@register class MyClass 等价于 MyClass = register(MyClass)。两种返回方式:① 返回原类(修改后)——最常见,def deco(cls): cls.新方法 = ...; return cls(给类加方法/属性/注册、类身份不变);② 返回替换品——如单例装饰器返回拦截实例化的函数(这时类身份变了、type(DB) 变成 function、isinstance 要小心)。典型用途:给类批量加方法(@dataclass 就是类装饰器、自动生成 __init__/__repr__/__eq__)、注册到表(插件系统 @register)、包装(单例/缓存/日志)、验证类。和函数装饰器区别只是目标不同(一个装饰函数常返回 wrapper 要用 functools.wraps、一个装饰类常返回修改后的原类不太需要 wraps),机制一样。和元类(metaclass)的区别:类装饰器在「类创建之后」对成品类做后处理(简单、够用大多数场景、但不影响子类——@deco class A 不会自动装饰 A 的子类 B),元类在「类创建的过程中」介入(更底层强大、能影响所有子类但更复杂);共识「能用类装饰器就别用元类」;要影响子类用 __init_subclass__(3.6+、轻量元类替代)或元类。一句话「类装饰器接收类、修改/增强(加方法/注册/包装)后返回类(@dataclass/@register);比函数装饰器只是目标是类;比元类简单(类创建后处理、不影响子类),能用装饰器就别用元类;返回替换品会改变类身份要小心;带参数用装饰器工厂」。