Python 的 __init_subclass__ 是什么?和元类有什么关系?
简化版
__init_subclass__ 是一个「子类钩子」(Python 3.6+,PEP 487)——在一个类被「定义为某个类的子类」时自动触发,让父类能对「每个新定义的子类」做处理(注册、校验、设默认值),是「轻量级元类」的替代方案。它定义在父类里(自动成为类方法,不用写 @classmethod),签名是 def __init_subclass__(cls, **kwargs)——这里的 cls 是「新定义的子类」(不是父类)。每当有人写 class Sub(Parent),Parent.__init_subclass__(cls=Sub) 就被自动调用。核心价值:以前「让所有子类自动应用某处理」(如自动注册到插件表、校验子类必须定义某属性)只能用元类(复杂、难懂),__init_subclass__ 用一个简单的钩子方法就搞定,且能影响所有子类(这是类装饰器做不到的——类装饰器只作用被装饰的那个类)。还能接收类定义时的关键字参数:class Sub(Parent, key=value) 里的 key=value 会传给 __init_subclass__ 的 **kwargs。核心记忆:__init_subclass__ 在子类被定义时自动触发(cls 是新子类),用于自动注册/校验所有子类,是元类的轻量替代(比元类简单、比类装饰器能影响子类)。
详细版
init_subclass 的特点:
| 特点 | 说明 |
|---|---|
| 触发时机 | 子类被「定义」时(class Sub(Parent)) |
| 定义位置 | 父类里 |
| cls 参数 | 「新定义的子类」(隐式类方法) |
| 影响范围 | 所有子类(含孙类) |
| 接收参数 | class Sub(Parent, key=v) 的 key=v |
| 定位 | 元类的轻量替代(PEP 487,3.6+) |
# 自动注册所有子类(插件系统)
class Plugin:
registry = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs) # 必须调 super
Plugin.registry[cls.__name__] = cls # cls 是新子类
class PluginA(Plugin): ... # 定义时自动注册
class PluginB(Plugin): ...
print(Plugin.registry) # {'PluginA': ..., 'PluginB': ...}
# 接收类定义时的关键字参数
class Base:
def __init_subclass__(cls, prefix="", **kwargs):
super().__init_subclass__(**kwargs)
cls.prefix = prefix # 用参数给子类设属性
class Child(Base, prefix="X"): # 传参数 prefix="X"
...
print(Child.prefix) # "X"
# 校验子类(强制子类定义某属性/方法)
class Model:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if not hasattr(cls, "table_name"):
raise TypeError(f"{cls.__name__} 必须定义 table_name")
class User(Model):
table_name = "users" # ✓
# class Bad(Model): pass # ✗ TypeError(没定义 table_name)
# cls 是子类,不是父类(关键理解)
class Parent:
def __init_subclass__(cls, **kwargs):
print(f"新子类: {cls.__name__}") # cls 是新定义的子类
super().__init_subclass__(**kwargs)
class A(Parent): pass # 打印 "新子类: A"
class B(A): pass # 打印 "新子类: B"(孙类也触发!)
# 对比:类装饰器只影响被装饰的类
# @register class A: ... # 只有 A 被注册
# class B(A): ... # B 不会自动注册(装饰器做不到)
⚠️ 核心理解:
__init_subclass__是「父类感知子类被创建」的钩子——你在父类里定义它,之后『每当有人写class Sub(父类)定义一个子类』,Python 就自动调用它一次,参数cls是『那个新定义的子类』。注意几个关键点:①cls是子类不是父类(__init_subclass__虽然定义在父类里,但被调用时 cls 传的是新子类,所以能对每个子类分别处理);② 它是隐式的类方法(Python 自动把它当 classmethod,不用也不该写@classmethod);③ 对『定义它的那个类本身』不触发(只对子类触发——class Plugin定义时不调、class PluginA(Plugin)时才调);④ 所有层级的子类都触发(子类、孙类、曾孙类,只要在继承链下面);⑤ 必须调super().__init_subclass__(**kwargs)(否则继承链上其他的__init_subclass__会被跳过、且**kwargs未消费会报错)。它是 PEP 487 引入的、专门用来「替代元类的常见用途」:以前「自动注册所有子类」「校验子类定义」「给所有子类设默认值」这些需求都得写元类(要懂type.__new__、影响面大、难调试),__init_subclass__用一个普通方法就搞定,更简单、更易懂。它和类装饰器的关键区别是「能影响所有子类」(类装饰器只作用被装饰的那一个类);和元类比则更轻量(元类能做更底层的事,但__init_subclass__覆盖了元类最常见的「定制子类创建」场景)。相关的还有__set_name__(描述符被赋值给类属性时触发,也是 PEP 487)。
完整版教学
一、init_subclass 是什么
先理解这个钩子的定位:
__init_subclass__:子类被定义时的钩子(PEP 487, Python 3.6+)
定义在"父类"里
每当有人定义"它的子类",就自动被调用
触发时机:
class Parent:
def __init_subclass__(cls, **kwargs):
... # 每次有子类被定义就调用
class Child(Parent): ... # ← 这行触发 Parent.__init_subclass__(cls=Child)
关键:cls 是"新定义的子类"(不是父类)
虽然定义在父类里,但被调用时 cls = 新子类
→ 所以能对每个子类分别处理
隐式类方法:
__init_subclass__ 自动是 classmethod
→ 不用写 @classmethod(Python 自动处理)
def __init_subclass__(cls, **kwargs): # cls 是子类
对定义它的类本身不触发:
class Plugin:
def __init_subclass__(cls): ...
→ 定义 Plugin 这行不触发(Plugin 不是自己的子类)
→ 只有 class Sub(Plugin) 才触发
定位:轻量级元类替代
元类能"定制类的创建",但复杂
__init_subclass__ 覆盖了元类最常见的用途(定制子类创建)
→ 用普通方法搞定,简单
所以__init_subclass__在子类被定义时自动触发(cls 是新子类),是元类的轻量替代
__init_subclass__:子类被定义时的钩子(PEP 487, Python 3.6+)——定义在「父类」里、每当有人定义「它的子类」就自动被调用。触发时机:class Child(Parent) 触发 Parent.__init_subclass__(cls=Child)。关键:cls 是「新定义的子类」(不是父类)——虽然定义在父类里、但被调用时 cls = 新子类(所以能对每个子类分别处理)。隐式类方法:__init_subclass__ 自动是 classmethod(不用写 @classmethod)。对定义它的类本身不触发:定义 Plugin 那行不触发(Plugin 不是自己的子类)、只有 class Sub(Plugin) 才触发。定位:轻量级元类替代(元类能定制类的创建但复杂、__init_subclass__ 覆盖了元类最常见的用途、用普通方法搞定简单)。理解「__init_subclass__在子类被定义时自动触发(class Child(Parent)调 Parent 的);cls 是新子类(不是父类,所以能对每个子类处理);隐式类方法(不用写@classmethod);对定义它的类本身不触发;是元类的轻量替代」,就理解了这个钩子。
二、自动注册子类
理解最经典的用途——注册:
自动注册所有子类(插件系统/工厂):
class Plugin:
registry = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs) # 必须调 super
Plugin.registry[cls.__name__] = cls
class PDFExporter(Plugin): ... # 定义时自动注册
class HTMLExporter(Plugin): ...
Plugin.registry # {'PDFExporter': ..., 'HTMLExporter': ...}
好处(对比手动注册):
① 无需手动登记(不用每个子类写 register())
② 不会漏(定义子类就自动注册)
③ 集中管理(注册逻辑在父类一处)
对比类装饰器注册(关键区别):
类装饰器:
@register class A: ... # A 注册了
class B(A): ... # B 不会自动注册!(装饰器只作用 A)
__init_subclass__:
class A(Plugin): ... # A 注册
class B(A): ... # B 也自动注册!(所有子类都触发)
→ __init_subclass__ 能影响"所有子类",类装饰器不能
用途场景:
① 插件系统(自动发现所有插件类)
② 序列化器/处理器注册表(按类型分派)
③ ORM 模型注册(所有 Model 子类)
④ 命令注册(所有 Command 子类)
所以自动注册子类:__init_subclass__里 registry[cls.__name__]=cls,所有子类自动注册(类装饰器做不到)
自动注册所有子类(插件系统/工厂):class Plugin: registry = {}; def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs); Plugin.registry[cls.__name__] = cls——class PDFExporter(Plugin) 定义时自动注册。好处(对比手动注册):① 无需手动登记(不用每个子类写 register())、② 不会漏(定义子类就自动注册)、③ 集中管理(注册逻辑在父类一处)。对比类装饰器注册(关键区别):类装饰器 @register class A 后 class B(A) 不会自动注册(装饰器只作用 A)、__init_subclass__ 里 class A(Plugin) 和 class B(A) 都自动注册(所有子类都触发)——__init_subclass__ 能影响「所有子类」、类装饰器不能。用途场景:插件系统、序列化器/处理器注册表、ORM 模型注册、命令注册。理解「自动注册子类:__init_subclass__里 registry[cls.name]=cls;好处无需手动登记+不会漏+集中管理;★对比类装饰器:类装饰器只注册被装饰的 A(B(A)不注册)、__init_subclass__所有子类都自动注册;用途插件系统/ORM/命令注册」,就掌握了自动注册。
三、校验子类与设默认值
理解校验和设置默认值的用途:
校验子类(强制子类满足约束):
class Model:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# 校验子类必须定义 table_name
if not hasattr(cls, "table_name"):
raise TypeError(f"{cls.__name__} 必须定义 table_name")
# 校验必须实现某方法
if not callable(getattr(cls, "save", None)):
raise TypeError("必须实现 save")
class User(Model):
table_name = "users" # ✓
# class Bad(Model): pass # ✗ 定义时就 TypeError(早暴露)
→ 在"子类定义时"就检查(比运行时调用才发现早)
给所有子类设默认值/属性:
class Base:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# 给每个子类加/初始化一些东西
cls._instances = [] # 每个子类独立的列表
if not hasattr(cls, "version"):
cls.version = "1.0" # 默认版本
自动处理子类的属性:
class AutoSlug:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# 根据类名自动生成 slug
cls.slug = cls.__name__.lower()
vs ABC 的强制:
ABC 用 @abstractmethod 强制"实现方法"(实例化时检查)
__init_subclass__ 能做更灵活的校验(定义时检查任意条件)
→ 校验"必须有某属性/满足某条件" → __init_subclass__ 更灵活
所以校验子类(定义时检查约束、比运行时早)、给所有子类设默认值/属性(cls.xxx=...)
校验子类(强制子类满足约束):if not hasattr(cls, "table_name"): raise TypeError——在「子类定义时」就检查(比运行时调用才发现早、class Bad(Model) 定义时就 TypeError)。给所有子类设默认值/属性:cls._instances = [](每个子类独立的列表)、if not hasattr(cls, "version"): cls.version = "1.0"(默认版本)。自动处理子类的属性:cls.slug = cls.__name__.lower()(根据类名自动生成 slug)。vs ABC 的强制:ABC 用 @abstractmethod 强制「实现方法」(实例化时检查)、__init_subclass__ 能做更灵活的校验(定义时检查任意条件、校验必须有某属性/满足某条件更灵活)。理解「校验子类:__init_subclass__里 if not hasattr(cls,‘x’):raise(定义时检查、比运行时早暴露);给所有子类设默认值 cls.xxx=…(每个子类独立);自动处理属性 cls.slug=cls.name.lower();比 ABC 灵活(能检查任意条件不只是方法)」,就掌握了校验和设默认值。
四、接收类定义时的关键字参数
理解 init_subclass 接收参数的机制:
类定义可以带"关键字参数",传给 __init_subclass__:
class Sub(Parent, key1=v1, key2=v2):
...
→ key1=v1, key2=v2 传给 Parent.__init_subclass__ 的 **kwargs
例子:
class Base:
def __init_subclass__(cls, prefix="", verbose=False, **kwargs):
super().__init_subclass__(**kwargs)
cls.prefix = prefix
if verbose: print(f"创建 {cls.__name__}, prefix={prefix}")
class Child(Base, prefix="X", verbose=True):
...
# 打印 "创建 Child, prefix=X"
Child.prefix # "X"
参数传递流程:
class Sub(Parent, custom=1):
→ Python 把 custom=1 从类定义里提取出来
→ 传给 __init_subclass__(cls=Sub, custom=1)
为什么要 super().__init_subclass__(**kwargs):
① 继承链上可能有多个 __init_subclass__(都要执行)
② object.__init_subclass__ 不接受额外参数
→ 你的 __init_subclass__ 要"消费"掉自己的参数
→ 剩下的(**kwargs)传给 super(最终到 object,应为空)
③ 不调 super 或没消费参数 → 报错 / 跳过其他钩子
配置化子类(实用):
class Handler:
def __init_subclass__(cls, route=None, **kwargs):
super().__init_subclass__(**kwargs)
if route:
routes[route] = cls
class UserHandler(Handler, route="/users"): ...
→ 声明式地把路由和类关联(类似装饰器但更声明式)
所以类定义可带关键字参数(class Sub(P,key=v))传给__init_subclass__的**kwargs;必须调 super 消费参数
类定义可以带「关键字参数」,传给 __init_subclass__:class Sub(Parent, key1=v1, key2=v2) → key1=v1、key2=v2 传给 Parent.__init_subclass__ 的 **kwargs。例子:def __init_subclass__(cls, prefix="", verbose=False, **kwargs) + class Child(Base, prefix="X", verbose=True)(cls.prefix = “X”)。参数传递流程:Python 把 custom=1 从类定义里提取出来、传给 __init_subclass__(cls=Sub, custom=1)。为什么要 super().__init_subclass__(**kwargs):① 继承链上可能有多个 __init_subclass__(都要执行)、② object.init_subclass 不接受额外参数(你的要「消费」掉自己的参数、剩下的传给 super 最终到 object 应为空)、③ 不调 super 或没消费参数会报错/跳过其他钩子。配置化子类(实用):class UserHandler(Handler, route="/users")(声明式地把路由和类关联)。理解「类定义可带关键字参数(class Sub(P,key=v))传给__init_subclass__的**kwargs;例子 prefix=‘X’给子类设属性;★必须调 super().init_subclass(**kwargs)消费参数(继承链多个钩子+object 不接受额外参数);配置化子类(route=‘/users’声明式关联)」,就掌握了接收参数的机制。
五、vs 元类 vs 类装饰器
理解三者的定位和选择:
三种"定制类"的机制:
类装饰器:
@deco class C: ...
① 类创建后处理
② 只作用被装饰的类(不影响子类)
③ 简单
→ 加方法/注册单个类
__init_subclass__:
父类里定义,子类创建时触发
① 影响"所有子类"(含孙类)
② 简单(普通方法)
③ 能校验/注册/设默认值/接收参数
→ "定制所有子类"的轻量方案
元类:
class C(metaclass=Meta): ...
① 类创建过程中介入(最底层)
② 影响所有子类
③ 复杂(要懂 type.__new__/__init__/__call__)
④ 能做更多(控制类体解析、修改 MRO、拦截实例化)
→ 最强大、最复杂
选择(从简单到复杂):
单个类加功能 → 类装饰器
所有子类注册/校验/设默认 → __init_subclass__(首选)
需要更底层的控制(类创建全过程) → 元类(最后手段)
PEP 487 的目的:
用 __init_subclass__ + __set_name__ 覆盖元类的大部分常见用途
→ 让大多数场景不用写元类(元类太难)
演进:
以前"自动注册子类" → 写元类(复杂)
现在 → __init_subclass__(简单)
所以三层:类装饰器(单类、简单)、__init_subclass__(所有子类、简单、首选)、元类(最底层、复杂、最后手段)
三种「定制类」的机制:类装饰器(@deco class C)——① 类创建后处理、② 只作用被装饰的类不影响子类、③ 简单(加方法/注册单个类);__init_subclass__(父类里定义、子类创建时触发)——① 影响「所有子类」(含孙类)、② 简单(普通方法)、③ 能校验/注册/设默认值/接收参数(「定制所有子类」的轻量方案);元类(class C(metaclass=Meta))——① 类创建过程中介入(最底层)、② 影响所有子类、③ 复杂(要懂 type.new/init/call)、④ 能做更多(控制类体解析/修改 MRO/拦截实例化,最强大最复杂)。选择(从简单到复杂):单个类加功能用类装饰器、所有子类注册/校验/设默认用 __init_subclass__(首选)、需要更底层控制用元类(最后手段)。PEP 487 的目的:用 __init_subclass__ + __set_name__ 覆盖元类的大部分常见用途(让大多数场景不用写元类)。理解「三层:类装饰器(单类、简单、不影响子类)、init_subclass(所有子类、简单、首选)、元类(最底层、复杂、最后手段);选择单类用装饰器、所有子类用__init_subclass__、底层控制用元类;PEP 487 让大多数不用写元类」,就掌握了三者的选择。
六、总结与实践
总结 init_subclass:
核心:
__init_subclass__ = 子类被定义时的钩子(3.6+)
定义在父类、每次子类被定义时自动调用
cls = 新定义的子类(隐式类方法)
触发:
class Sub(Parent): ... → Parent.__init_subclass__(cls=Sub)
所有层级子类都触发(子类、孙类...)
对定义它的类本身不触发
用途:
① 自动注册所有子类(插件/工厂)
② 校验子类(定义时检查约束,早暴露)
③ 给所有子类设默认值/属性
④ 接收类定义时的关键字参数(class Sub(P, key=v))
关键规则:
必须调 super().__init_subclass__(**kwargs)
(消费参数 + 让继承链其他钩子执行)
vs 类装饰器 vs 元类:
类装饰器:单个类、不影响子类、简单
__init_subclass__:所有子类、简单、首选
元类:最底层、复杂、最后手段
实践建议:
① 要"定制所有子类"(注册/校验)→ __init_subclass__(别急着用元类)
② 记得调 super
③ cls 是子类(不是父类)
④ 配合 __set_name__(描述符命名)
核心总结:
__init_subclass__ 子类定义时触发(cls 是子类)
自动注册/校验所有子类
元类的轻量替代(简单、能影响子类)
所以__init_subclass__子类定义时触发(cls 是子类),自动注册/校验所有子类,元类轻量替代
核心:__init_subclass__ = 子类被定义时的钩子(3.6+),定义在父类、每次子类被定义时自动调用、cls = 新定义的子类(隐式类方法)。触发:class Sub(Parent) → Parent.__init_subclass__(cls=Sub)、所有层级子类都触发、对定义它的类本身不触发。用途:① 自动注册所有子类、② 校验子类(定义时检查约束早暴露)、③ 给所有子类设默认值/属性、④ 接收类定义时的关键字参数。关键规则:必须调 super().__init_subclass__(**kwargs)(消费参数 + 让继承链其他钩子执行)。vs 类装饰器 vs 元类:类装饰器(单个类、不影响子类、简单)、__init_subclass__(所有子类、简单、首选)、元类(最底层、复杂、最后手段)。理解「__init_subclass__子类定义时触发(cls 是子类、所有层级);用途注册/校验/设默认/接收参数;必须调 super;比类装饰器能影响所有子类、比元类简单;定制所有子类首选它别急着用元类」,就掌握了总结与实践。
记忆钩子:「init_subclass__是一个『子类钩子』(Python 3.6+/PEP 487)——在一个类被『定义为某个类的子类』时自动触发,让父类能对『每个新定义的子类』做处理(注册/校验/设默认值),是『轻量级元类』的替代方案;定义在父类里(自动成为类方法、不用写@classmethod),签名 def init_subclass(cls,kwargs)——★这里的 cls 是『新定义的子类』(不是父类!),每当有人写 class Sub(Parent),Parent.init_subclass(cls=Sub)就被自动调用(所有层级子类、孙类都触发,但对定义它的类本身不触发);★核心价值:以前『让所有子类自动应用某处理』(自动注册到插件表、校验子类必须定义某属性)只能用元类(复杂难懂),__init_subclass__用一个简单钩子搞定,且★能影响所有子类(这是类装饰器做不到的——@register class A 只注册 A、class B(A)不会自动注册,而__init_subclass__里 A 和 B(A)都自动注册);★还能接收类定义时的关键字参数:class Sub(Parent,key=value)里的 key=value 会传给__init_subclass__的kwargs(用于配置化子类如 class Handler(Base,route=‘/users’));★必须调 super().init_subclass(**kwargs)(消费自己的参数+让继承链上其他__init_subclass__执行,object 的不接受额外参数);三层选择:单个类加功能用类装饰器、定制所有子类(注册/校验)用__init_subclass(首选、别急着用元类)、需要最底层控制才用元类;相关还有__set_name__(描述符被赋值给类属性时触发)」。
七、常见误区与追问
- 误区:init_subclass 里的 cls 是定义它的那个父类。 不是——cls 是「新定义的子类」:
__init_subclass__虽然定义在父类里,但它被调用是因为「有子类被定义」,此时传入的 cls 就是那个新子类,而不是父类;这正是它能「对每个子类分别处理」(注册子类、校验子类、给子类设属性)的关键;比如class A(Plugin)触发时 cls 是 A、class B(Plugin)触发时 cls 是 B。 - 误区:定义 init_subclass 的那个类本身也会触发它。 不会——
__init_subclass__只在「它的子类被定义时」触发,对「定义它的那个类本身」不触发:class Plugin: def __init_subclass__(cls): ...这行定义 Plugin 时不会调用__init_subclass__(因为 Plugin 不是自己的子类),只有后面class PluginA(Plugin)才触发;所以基类本身不会被注册/校验,只有子类会。 - 误区:init_subclass 需要写 @classmethod 装饰器。 不用、也不该——Python 会自动把
__init_subclass__当作类方法处理(隐式的 classmethod),你直接写def __init_subclass__(cls, **kwargs):即可,第一个参数 cls 会自动接收到子类;如果你手动加@classmethod是多余的(虽然通常也不会出错,但不符合惯例);这和__set_name__、__class_getitem__类似,都是 Python 特殊处理的钩子。 - 误区:init_subclass 里不调用 super() 也没关系。 有关系——应该调用
super().__init_subclass__(**kwargs):① 继承链上可能有多个类都定义了__init_subclass__(多重继承或多层继承),不调 super 会跳过其他类的钩子、导致它们的注册/校验逻辑不执行;② 如果类定义带了关键字参数(class Sub(Parent, key=v)),这些参数会进入**kwargs,你的__init_subclass__应该「消费」掉自己认识的参数、把剩下的通过super().__init_subclass__(**kwargs)往上传,最终object.__init_subclass__不接受任何额外参数(如果 kwargs 非空会报错);所以约定俗成要调 super、并正确处理 kwargs。 - 追问:init_subclass 和类装饰器都能给类做处理,有什么区别? 关键区别是「影响范围」:类装饰器只作用于「被它直接装饰的那一个类」,不会自动应用到子类——
@register class A: ...只会注册 A,之后class B(A): ...不会被自动注册(除非 B 也显式加 @register);而__init_subclass__定义在父类里、会对「所有子类(包括孙类等所有层级)」自动触发——class A(Plugin)、class B(A)都会自动执行父类的__init_subclass__(注册、校验等);所以当你需要「让某个基类的所有后代类都自动应用某处理」(如插件系统自动收集所有插件子类、ORM 自动注册所有 Model 子类、强制所有子类满足某约束)时,用__init_subclass__而不是类装饰器;类装饰器适合「针对单个类的一次性处理」。 - 追问:init_subclass 和元类是什么关系,什么时候用哪个?
__init_subclass__(PEP 487,Python 3.6+)是专门为「替代元类的常见用途」而引入的轻量方案:以前「自动注册所有子类、校验子类定义、给所有子类设默认值」这些需求都得写元类(需要理解type.__new__/__init__、影响面大、难调试、多个元类还会冲突),而__init_subclass__用一个定义在父类里的普通方法就搞定,简单易懂、还能接收类定义时的关键字参数;关系上,__init_subclass__覆盖了元类「定制子类创建」这个最常见的场景;选择原则:需要「对所有子类做注册/校验/设默认值」这类「子类创建后的处理」,优先用__init_subclass__(几乎不用再写元类);只有当你需要更底层的控制——比如干预类体的解析过程、修改类的 MRO、拦截类的实例化(__call__)、或者要控制「类本身是如何被 type 造出来的」——这些__init_subclass__做不到的事,才用元类;一句话:能用__init_subclass__就别用元类,元类是最后的、最强大也最复杂的手段。 - 追问:怎么用 init_subclass 实现「所有子类自动注册 + 强制子类定义某属性」? 在父类里定义
__init_subclass__,把注册和校验逻辑写在里面:class Plugin:registry = {}def __init_subclass__(cls, **kwargs):super().__init_subclass__(**kwargs)(先调 super)if not hasattr(cls, "name"):raise TypeError(f"{cls.__name__} 必须定义 name 属性")(校验:子类没定义 name 就在「定义时」报错、提前暴露)Plugin.registry[cls.name] = cls(注册:用子类的 name 作键把子类登记到注册表);之后每定义一个子类class PDFPlugin(Plugin): name = "pdf",Python 就自动调用一次这个钩子、先校验它有没有 name、再把它注册进 registry;如果写class BadPlugin(Plugin): pass(没定义 name),在「定义这个类」的时候就会抛 TypeError(不用等到实例化或调用);这样一个简单的钩子方法就同时实现了「自动注册所有插件子类」和「强制子类遵守约定」,比写元类简洁得多。
八、加强记忆
__init_subclass__ 是一个「子类钩子」(Python 3.6+/PEP 487)——在一个类被「定义为某个类的子类」时自动触发,让父类能对「每个新定义的子类」做处理(注册/校验/设默认值),是「轻量级元类」的替代方案。定义在父类里(自动成为类方法、不用写 @classmethod),签名 def __init_subclass__(cls, **kwargs)——这里的 cls 是「新定义的子类」(不是父类!),每当有人写 class Sub(Parent),Parent.__init_subclass__(cls=Sub) 就被自动调用(所有层级子类、孙类都触发,但对定义它的类本身不触发)。核心价值:以前「让所有子类自动应用某处理」(自动注册到插件表、校验子类必须定义某属性)只能用元类(复杂难懂),__init_subclass__ 用一个简单钩子搞定,且能影响所有子类(这是类装饰器做不到的——@register class A 只注册 A、class B(A) 不会自动注册,而 __init_subclass__ 里 A 和 B(A) 都自动注册)。还能接收类定义时的关键字参数:class Sub(Parent, key=value) 里的 key=value 传给 __init_subclass__ 的 **kwargs(用于配置化子类如 class Handler(Base, route="/users"))。必须调 super().__init_subclass__(**kwargs)(消费自己的参数 + 让继承链上其他 __init_subclass__ 执行)。三层选择:单个类加功能用类装饰器、定制所有子类(注册/校验)用 __init_subclass__(首选、别急着用元类)、需要最底层控制才用元类。一句话「init_subclass 在子类被定义时自动触发(cls 是新子类、所有层级)、用于自动注册/校验所有子类、是元类的轻量替代(比元类简单、比类装饰器能影响所有子类);必须调 super、能接收类定义时的关键字参数」。