当只需在类定义时执行注册、校验或注入,默认用__init_subclass__;它不能修改类结构、拦截属性访问或改变类类型,而元类可深度控制类创建过程。

什么时候该用 __init_subclass__ 而不是元类
当你只需要在类被定义(而非实例化)时执行一次性注册、参数校验或默认属性注入,__init_subclass__ 就够了;元类是重写类创建流程的“手术刀”,而 __init_subclass__ 是贴在子类定义末尾的“便签条”。绝大多数注册表、插件系统、ORM 模型基类场景,都不需要动元类。
__init_subclass__ 的基本写法和参数传递
它在每个直接子类定义完成时自动调用,接收子类对象本身 + 所有传给 class 语句的关键字参数。注意:父类的 __init_subclass__ 不会自动继承,必须显式调用 super() 否则链会断。
常见写法:
class PluginBase:
plugins = {}
<pre class="brush:php;toolbar:false;">def __init_subclass__(cls, name=None, **kwargs):
super().__init_subclass__(**kwargs) # 必须这行,否则多层继承失效
if name is not None:
cls.plugins[name] = clsclass Exporter(PluginBase, name="csv"): # 这里传入 name pass
-
name=None是你自定义的参数,必须显式声明为函数参数才能捕获 -
**kwargs用来兜底未声明的参数,避免TypeError - 不写
super().__init_subclass__(**kwargs)→ 子类再继承Exporter时,__init_subclass__就不会触发
和元类比,它不能做什么
__init_subclass__ 在类体执行完、MRO 确定后才运行,它无法:
立即学习“Python免费学习笔记(深入)”;
- 修改类的
__dict__结构(比如动态加方法到类体中) - 拦截或重写属性访问逻辑(那是
__getattribute__或描述符的事) - 改变类的类型(即不能把
class A:变成class A(metaclass=MyMeta):那种层级的控制) - 访问类正在解析的 AST 或装饰器原始信息(元类的
__new__可以拿到name,bases,namespace)
如果你看到代码里元类只干了一件事:往某个全局字典里注册子类——那基本可以安全替换成 __init_subclass__。
容易忽略的继承陷阱
多个父类都定义了 __init_subclass__?Python 会按 MRO 顺序依次调用,但每个都必须主动调用 super(),否则链在第一个没调用的地方就断了。调试时最常踩的坑是:
- 父类写了
super().__init_subclass__(**kwargs),但子类覆盖时忘了加**kwargs→ 报TypeError: object.__init_subclass__() takes no parameters - 用
super().__init_subclass__()(没传**kwargs)→ 后续父类收不到自定义参数 - 在
__init_subclass__里访问子类尚未完成初始化的属性(比如cls.__annotations__通常可用,但cls.some_classvar若依赖其他装饰器,可能还没就位)
复杂继承树下,建议统一用 super().__init_subclass__(**kwargs),并始终把未处理的 **kwargs 透传下去。


















