← 返回题目列表

Python 的 __init_subclass__ 是什么?和元类有什么关系?

困难 第 21 / 21 题 更新于 2026/07/31
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 Aclass 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、能接收类定义时的关键字参数」。