应优先用 __init_subclass__ 替代元类,它更轻量直观,适用于子类注册、字段校验等场景;仅当需拦截类创建全过程(如改写 __new__、__prepare__)时才用元类。

直接说结论:用 __init_subclass__ 替代简单元类,是更轻量、更直观、更符合 Python 3.6+ 设计意图的做法——只要你的需求只是“在子类定义时自动执行一段逻辑”,就别写元类。
什么时候该用 __init_subclass__ 而不是元类
它专为“子类注册”“字段校验”“自动装饰方法”这类常见场景设计,本质是 Python 自动在每次 class 语句结束时调用父类的 __init_subclass__ 方法。元类则介入整个类对象构造过程,过度杀伤。
- 你需要在每个子类定义后立刻做点事(比如加进全局 registry)→ 用
__init_subclass__ - 你要修改类的
__dict__、动态重写方法名、强制所有子类带某个属性 → 还是__init_subclass__就够了 - 你要拦截
type(...)的调用、统一改写所有类的__new__行为、或和__prepare__配合做命名空间预处理 → 才需要元类
__init_subclass__ 的参数怎么传、怎么用
它接收的是子类定义时显式传入的关键字参数,不是 **kwargs 黑盒。Python 会把 class Child(Parent, some_flag=True, version="1.2"): 中的 some_flag 和 version 直接传进来。
- 必须定义为类方法(
@classmethod),且第一个参数是cls(即刚定义好的子类) - 第二个及之后的参数对应 class 语句里的关键字参数,不传就不会出现在签名里
- 如果父类没定义
__init_subclass__,Python 会回退到object.__init_subclass__,它只接受空参数;所以一旦你加了自定义参数,就必须显式覆盖父类方法
class Plugin:
registry = {}
def __init_subclass__(cls, name=None, **kwargs):
super().__init_subclass__(**kwargs) # 必须转发未识别的 kwargs
if name is not None:
cls.registry[name] = cls
<p>class HTTPHandler(Plugin, name="http"):
pass
</p>
容易踩的坑:继承链、参数转发与 super()
多个父类都定义了 __init_subclass__?Python 按 MRO 顺序调用,但每个都必须显式处理未知参数,否则会报 TypeError: __init_subclass__() takes no keyword arguments。
- 永远在自定义实现里调用
super().__init_subclass__(**kwargs),把未消费的参数继续往下传 - 不要试图在
__init_subclass__里访问子类实例属性(还没创建),也不能调用子类的__init__(根本不存在) - 它不能返回值,也不影响类对象本身构造结果——想改类,只能直接操作
cls.__dict__或用setattr
和元类比,性能与可读性差在哪
几乎没有差。反而是更好:没有额外的 metaclass= 声明,不用理解 __new__ 和 __init__ 在元类中的分工,调试时堆栈也干净得多。
- 元类的
__new__在类对象创建前运行,__init_subclass__在创建后、首次访问前运行——对绝大多数注册/标记场景,时机差异无实质影响 - 如果你发现
__init_subclass__不够用,往往说明你真需要元类了;但这时候问题通常已超出“定制子类”的范畴,而是“控制类的诞生方式”本身
真正容易被忽略的,是那个 **kwargs 转发——漏掉它,多层继承下第一个不转发的父类就会截断参数,后续类收不到配置,而且错误提示非常不直观。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











