__init_subclass__ 应在子类定义时强制校验字段、注册类型或统一初始化逻辑时使用,它于子类定义完成、实例化前触发,仅执行一次,不适用于实例级约束。

__init_subclass__ 什么时候该用?
当你需要在子类定义时就强制校验字段、注册类型,或者统一初始化逻辑,而不是等到实例化才检查——这时候 __init_subclass__ 是唯一能介入的时机。它比 __new__ 更轻量,比装饰器更原生,且只在继承发生时触发一次。
常见误用是拿它替代 __init__:它不处理实例,只处理类本身。如果只是想约束实例属性,应该用 __post_init__(dataclass)或 __setattr__。
如何用 __init_subclass__ 校验子类必须定义某些字段?
典型场景:所有子类必须声明 schema 或 required_fields 这类元数据,否则无法注册为合法类型。直接在 __init_subclass__ 中检查类属性是否存在并符合类型即可。
- 检查字段是否存在:
if not hasattr(cls, 'required_fields'): - 检查是否为 tuple/list:
isinstance(cls.required_fields, (tuple, list)) - 避免覆盖父类已设默认值:用
cls.__dict__.get('required_fields') is not None区分显式定义和继承而来 - 报错用
TypeError,不是ValueError—— 这是类构造失败,不是数据错误
示例:
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
立即学习“Python免费学习笔记(深入)”;
class BaseRecord:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if not hasattr(cls, 'required_fields'):
raise TypeError(f"{cls.__name__} must define 'required_fields'")
if not isinstance(cls.required_fields, (tuple, list)):
raise TypeError(f"{cls.__name__}.required_fields must be tuple or list")
<p>class UserRecord(BaseRecord):
required_fields = ('id', 'name') # ✅ 合法__init_subclass__ 和 metaclass 冲突吗?
会。如果父类用了自定义 metaclass,而子类又定义了 __init_subclass__,Python 会优先调用 metaclass 的 __new__,再触发 __init_subclass__。但两者目的不同:metaclass 控制类对象创建,__init_subclass__ 只做后置校验或注册。
- 不要在
__init_subclass__里修改cls.__dict__或动态加方法——这属于 metaclass 职责 - 如果已有 metaclass,且它也做了类似校验,重复检查可能引发冗余报错
- 兼容写法:在
__init_subclass__开头加if hasattr(cls, '_skip_subclass_check'):做绕过开关
注册子类时容易漏掉的细节
用 __init_subclass__ 自动注册子类到全局 registry 很常见,但几个坑几乎必踩:
- 注册用
cls,不是self—— 它没有 self - 避免重复注册:同一类被 import 多次时,
__init_subclass__仍只触发一次;但若热重载或测试中反复 exec,需加if cls not in registry: - 别在 registry 里存字符串名代替类对象——后续无法获取注解、方法等运行时信息
- 如果子类继承链中有中间抽象类(比如
BaseRecord → ValidatedRecord → UserRecord),默认也会触发注册,要用cls.__abstractmethods__或标记属性跳过
真正麻烦的是跨模块注册顺序:A 模块定义基类,B 模块定义子类,但 C 模块导入 B 前就访问 registry —— 此时子类还没加载。这不是 __init_subclass__ 的问题,而是 Python 导入机制决定的,得靠文档或延迟初始化兜底。

















