《Python高级编程》2.1 元类与 __init_subclass__

本节从 CPython 实现角度拆解 class 语句:它如何脱糖成 __build_class__ 与 type() 三参数调用,元类的 __prepare__、__new__、__init__ 与 __set_name__、__init_subclass__ 的精确执行顺序,元类冲突背后的 MRO 规则,以及何时该用 __init_subclass__ 替代元类。附实测时序与性能数据。

本节目标:从 CPython 实现角度讲清 class 语句背后发生了什么,以及元类钩子与 __init_subclass__ 的精确执行时序。
适用版本:Python 3.12+(实测 3.14.6)

2.1 元类与 __init_subclass__

站内专题 Python 元编程与动态特性深度解析 已经讲过元类「怎么用」:type() 三参数建类、单例元类、自动注册元类、元类冲突的解决办法。这一节不再重复用法,而是往下挖一层——class 语句在 CPython 里到底翻译成了什么,以及那些钩子函数在毫秒级别上的真实调用顺序。这些顺序靠读文档是记不准的,只能靠实测。

2.1.1 class 语句脱糖成了什么

先看一段最普通的类定义,用 dis 把它编译后的字节码打印出来:

import dis

def make():
    class C:
        x = 1
        def f(self):
            return 1
    return C

dis.dis(make)

真实输出(Python 3.14.6,节选):

  5           LOAD_BUILD_CLASS
              PUSH_NULL
              LOAD_CONST               0 (<code object C ...>)
              MAKE_FUNCTION
              LOAD_CONST               1 ('C')
              CALL                     2
              STORE_FAST               0 (C)

关键信息全在这里:class 语句并不是一条「原子指令」,它编译成了一个对内置函数 __build_class__ 的调用。参数是两个:一个是类体的代码对象(MAKE_FUNCTION 把它包成函数),另一个是类名字符串 'C'。

也就是说,class C: 大致等价于:

def __class_body__():
    x = 1
    def f(self):
        return 1
    return locals()

C = __build_class__(__class_body__, "C")

类体是一段独立编译的代码对象——这是理解后面所有钩子时序的前提。类体里的语句在「类对象存在之前」就已经执行完了,它的产物是一个普通的命名空间字典。

再看类体自身的字节码(同一个 dis.dis 输出的后半段):

  --           MAKE_CELL                0 (__classdict__)
   5           RESUME                   0
               LOAD_NAME                0 (__name__)
               STORE_NAME               1 (__module__)
               LOAD_CONST               0 ('make.<locals>.C')
               STORE_NAME               2 (__qualname__)
               LOAD_SMALL_INT           5
               STORE_NAME               3 (__firstlineno__)
               LOAD_LOCALS
               STORE_DEREF              0 (__classdict__)
   6           LOAD_SMALL_INT           1
               STORE_NAME               4 (x)
   7           LOAD_CONST               1 (<code object f ...>)
               MAKE_FUNCTION
               STORE_NAME               5 (f)
               LOAD_CONST               2 (())
               STORE_NAME               6 (__static_attributes__)
               LOAD_FAST_BORROW         0 (__classdict__)
               STORE_NAME               7 (__classdictcell__)

编译器往命名空间里自动塞了几个键:__module__、__qualname__、__firstlineno__、__static_attributes__,以及 3.14 特有的 __classdictcell__。这就是为什么上一节 2.1.2 的实验里,元类 __new__ 收到的 namespace 里有你没写的键。其中 __firstlineno__(类定义起始行号)和 __static_attributes__(类体函数中通过 self.X 访问过的属性名)都是 3.13 才引入的数据模型改进。

2.1.2 元类钩子的精确时序

文档只会笼统地说「元类可以介入类的创建」。到底谁先谁后?下面这段代码把每个钩子都插了打印,跑一次就清楚了:

class Meta(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kw):
        print("1. __prepare__", name)
        return {}
    def __new__(mcs, name, bases, ns, **kw):
        print("3. __new__ 收到 namespace keys:", list(ns))
        cls = super().__new__(mcs, name, bases, ns)
        print("4. __new__ 返回类对象", cls)
        return cls
    def __init__(cls, name, bases, ns, **kw):
        print("6. __init__", cls)
        super().__init__(name, bases, ns)

class Desc:
    def __set_name__(self, owner, name):
        print("5a. __set_name__", owner.__name__, name)

class Base:
    def __init_subclass__(cls, **kw):
        print("5b. __init_subclass__ 父类收到子类", cls.__name__)

class C(Base, metaclass=Meta):
    print("2. 类体执行")
    x = 1
    d = Desc()

真实输出:

1. __prepare__ C
2. 类体执行
3. __new__ 收到 namespace keys: ['__module__', '__qualname__', '__firstlineno__', 'x', 'd', '__static_attributes__']
5a. __set_name__ C d
5b. __init_subclass__ 父类收到子类 C
4. __new__ 返回类对象 <class '__main__.C'>
6. __init__ <class '__main__.C'>

完整顺序是:

序阶段说明
1__prepare__元类类方法,返回类体要写入的命名空间映射
2类体执行所有类体语句在此运行,结果写进 __prepare__ 返回的映射
3元类 __new__开始构造类对象
4__set_name__在 super().__new__ 内部对每个描述符调用
5__init_subclass__在 super().__new__ 内部,对父类调用
6元类 __init__元类 __new__ 返回后调用

两个反直觉的点:

  1. __set_name__ 和 __init_subclass__ 都发生在 __new__ 内部(第 4、5 步夹在第 3 步和第 4 步的返回之间),而不是在 __init__ 之后。因为它们在 CPython 里是由 type.__new__ 亲自触发的。
  2. __set_name__ 先于 __init_subclass__。所以如果父类的 __init_subclass__ 想读取子类描述符已绑定的名字,此时已经就绪。

2.1.3 type() 三参数:同一套机制的裸调用

class 语句最终走的是 type.__call__(mcls, name, bases, ns) → mcls.__new__ → mcls.__init__。那么直接用 type(name, bases, ns) 建类,会不会漏掉 __set_name__ 和 __init_subclass__?

class Desc:
    def __set_name__(self, owner, name):
        print("  __set_name__ 触发:", owner.__name__, name)

class Base:
    def __init_subclass__(cls, **kw):
        print("  __init_subclass__ 触发:", cls.__name__)

T = type('T', (Base,), {'d': Desc()})
print("type(T) =", type(T).__name__)

真实输出:

  __set_name__ 触发: T d
  __init_subclass__ 触发: T
type(T) = type

结论:type() 三参数照样触发这两个钩子,因为它调用的就是同一个 type.__new__。它也不会漏掉元类推导——如果基类的元类是 Meta,用 type('T2', (A,), {}) 建出的类,type(T2) 同样是 Meta。

type() 三参数唯一绕过的是 __prepare__:它直接接收一个现成的字典,不经过 __prepare__。所以「需要自定义命名空间映射」是少数必须走 class 语句 + 元类的场景。

那用 class 语句比 type() 贵多少?实测 20 万次建一个最小类:

type() 三参数: 3.982 µs/次
class 语句  : 4.089 µs/次
比值: 1.03x

差距只有 3%——class 语句本身就是 __build_class__ 对 type() 的一层薄封装。「用 type() 动态建类更快」是个流传很广的误解,两者开销几乎相同。

2.1.4 __prepare__ 真正的用途

__prepare__ 的返回值就是类体执行时用的命名空间。默认返回一个普通 dict,你可以换成任何映射对象。一个实际用途是保留成员的定义顺序——虽然普通 dict 从 3.7 起也保序,但你可以借此记录一份「只属于本类、不含自动注入键」的清单:

import collections

class OrderedMeta(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kw):
        return collections.OrderedDict()
    def __new__(mcs, name, bases, ns, **kw):
        cls = super().__new__(mcs, name, bases, dict(ns))
        cls._defined = [k for k in ns if not k.startswith('__')]
        return cls

class Ordered(metaclass=OrderedMeta):
    z = 1
    a = 2
    m = 3

print("定义顺序:", Ordered._defined)

真实输出:

定义顺序: ['z', 'a', 'm']

注意 dict(ns) 这一步:因为 __new__ 传给 type.__new__ 的命名空间会被存成类的 __dict__,而 CPython 期望它是真正的 dict,所以最好在交给父类前转换一次。定义顺序按源码书写顺序(z, a, m),不是字母序——这正是 dataclasses、ORM 字段排序依赖的机制。

2.1.5 元类冲突的本质是 MRO

当两个基类用不同元类时,Python 报:

metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

这条规则可以用一句话概括:子类的元类,必须是所有基类元类的(非严格)子类。所以解决方式不是「随便选一个」,而是造一个同时继承自两者的元类:

class MetaA(type): pass
class MetaB(type): pass
class A(metaclass=MetaA): pass
class B(metaclass=MetaB): pass

class MetaAB(MetaA, MetaB): pass
class C2(A, B, metaclass=MetaAB): pass

print("type(C2) =", type(C2).__name__)          # MetaAB
print("MetaAB.__mro__ =", [c.__name__ for c in MetaAB.__mro__])

真实输出:

解决后 type(C2) = MetaAB
MetaAB.__mro__ = ['MetaAB', 'MetaA', 'MetaB', 'type', 'object']

这解释了为什么框架里的元类总是尽量继承 type 并保持浅层:每多一个不相关的元类,用户组合多个 mixin 基类时就越容易撞上冲突。

2.1.6 __init_subclass__:大多数时候不需要元类

专题里的「自动注册插件」示例用了元类。但同样的需求,__init_subclass__ 就能完成,而且更轻:

class BaseSub:
    registry = {}
    def __init_subclass__(cls, **kw):
        super().__init_subclass__(**kw)
        BaseSub.registry[cls.__name__] = cls

class Q1(BaseSub): pass
class Q2(BaseSub): pass
print("__init_subclass__ 注册表:", sorted(BaseSub.registry))

真实输出:

__init_subclass__ 注册表: ['Q1', 'Q2']

__init_subclass__ 有几个容易忽略的语义:

  • 它隐式是一个 classmethod,定义时不需要 @classmethod,第一个参数就是新创建的子类。
  • 它从父类一侧被调用:Base.__init_subclass__(Sub)。
  • 定义在 class 语句上的关键字参数会透传进来:class Sub(Base, tag="a") 会让 __init_subclass__(cls, tag="a") 收到 tag。多级继承时记得 super().__init_subclass__(**kw) 把参数继续往上传递。

选型规则:

需求用 __init_subclass__用元类
子类创建后注册/校验/改写类属性✅可以但过重
修改本类自身的创建过程(命名空间、类对象)❌✅
需要自定义命名空间映射(__prepare__)❌✅
想拦截实例的创建(如单例,重写 __call__)❌✅
不引入额外元类、避免组合冲突✅❌

一句话:__init_subclass__ 处理「子类建成之后」,元类处理「类本身怎么建成」。能用前者就别上后者,元类每多一个都会增加下游使用者遇到 metaclass conflict 的概率。

2.1.7 3.13+ 的类命名空间新键

实测顺带确认了两个 3.13 引入的类属性,它们直接来自类体字节码的自动注入:

class P:
    def __init__(self):
        self.x = 1
        self.y = 2

print("__static_attributes__ =", P.__static_attributes__)

真实输出:

__static_attributes__ = ('x', 'y')
  • __static_attributes__:类中所有函数通过 self.X 访问过的属性名元组,按字母序。它是给解释器优化(尤其自适应/JIT)用的,能预先知道实例可能有哪些属性。
  • __firstlineno__:类定义的起始行号,方便工具报错定位。

这两个键是版本差异:3.12 及更早的类命名空间里没有它们。如果你的元类 __new__ 遍历命名空间做处理,需要把这两个键也纳入考虑(比如 2.1.4 里用 startswith('__') 过滤时刚好能排除掉)。

小结

  • class 语句编译成对 __build_class__ 的调用,类体是一段独立代码对象;type() 三参数走的是同一套 type.__new__。
  • 元类钩子的精确顺序:__prepare__ → 类体 → __new__ →(内部)__set_name__ →(内部)__init_subclass__ → __init__。
  • type() 三参数同样触发 __set_name__ 与 __init_subclass__,也会做元类推导;唯一绕过的是 __prepare__。
  • class 语句与 type() 建类性能几乎相同(实测 1.03x),「动态建类更快」是误解。
  • 元类冲突的本质是「子类元类必须是基类元类的子类」;__init_subclass__ 能覆盖大多数注册/校验需求,应优先使用。
  • __static_attributes__ / __firstlineno__ 是 3.13 新增的类命名空间键。

理解了「类怎么被造出来」,下一步就是「类造好之后还能怎么改」。下一节我们看类装饰器、__set_name__ 与属性工厂——它们恰好都发生在 2.1.2 那张时序表的第 4 步之后。

阅读导航:上一节:1.3 魔术方法驱动的协议设计 · 下一节:2.2 类装饰器、set_name 与属性工厂 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「python」更多文章

  1. 《Python高级编程》目录
  2. 《Python高级编程》11.3 PEP 流程与版本迁移策略
  3. 《Python高级编程》11.2 嵌入式与自由线程运行时