Python 基础体系 · 第 29/112 篇。示例统一以 Python 3.14 为语言基线;第三方库使用与其兼容的现代稳定版本,版本敏感行为会单独说明。

Python 元类:类创建流程、prepare、注册和使用边界

元类(metaclass)是“创建类的类”。

普通对象由类创建:

class User:
    pass

user = User()

这里,userUser 的实例:

type(user) is User

User 自身也是一个对象,它的类型通常是 type

type(User) is type

因此可以把关系写成:

user ──实例化──> User ──实例化──> type

元类改变的是第二条边:它控制“类对象如何被创建”。它通常不负责普通实例的初始化,而是负责类定义语句执行结束后,如何把类名、基类和类命名空间组装成一个新的类对象。

Python 官方将类创建过程分为几个阶段:解析 MRO 条目、确定适用的元类、准备类命名空间、执行类主体、创建类对象;之后还可能执行 __set_name__()__init_subclass__() 和类装饰器。(docs.python.org)


一、先区分三个不同的“创建”

理解元类之前,必须把以下三个过程分开:

  1. 执行 class 语句;
  2. 创建类对象;
  3. 使用类对象创建实例。

例如:

class User:
    role = "guest"

    def __init__(self, name):
        self.name = name


user = User("Alice")

这段代码至少包含两次不同层次的调用。

1. 执行类定义

执行:

class User:
    ...

Python 会执行类主体,并收集类主体产生的命名空间:

{
    "__module__": "...",
    "__qualname__": "User",
    "role": "guest",
    "__init__": <function User.__init__>,
}

随后,Python 使用元类创建 User 类对象。

2. 创建类对象

默认情况下,相当于调用:

User = type(
    "User",
    (),
    {
        "__module__": "...",
        "__qualname__": "User",
        "role": "guest",
        "__init__": User.__init__,
    },
)

这不是普通实例化,而是调用 type 创建一个新的类对象。

3. 创建普通实例

执行:

user = User("Alice")

才是使用 User 这个类对象创建普通实例。此时通常会经过 type.__call__(),再调用 User.__new__()User.__init__()

可以用下面的关系概括:

class User: ...       --元类-->  User 类对象
User("Alice")         --类对象--> user 实例

元类主要介入第一个箭头;普通类中的 __new__()__init__() 主要介入第二个箭头。


二、默认元类为什么是 type

如果没有显式指定基类和元类:

class User:
    pass

它默认继承自 object,并使用 type 作为元类:

class User(object, metaclass=type):
    pass

这不是要求源码必须这样写,而是对默认行为的近似表达。类主体会在新的局部命名空间中执行,执行完成后,该命名空间被用于创建类对象。(docs.python.org)

可以验证:

class User:
    pass

print(User.__bases__)
print(type(User))
print(type(object))

预期输出:

(<class 'object'>,)
<class 'type'>
<class 'type'>

type 自身也是一个对象,并且:

type(type) is type

这是一个自洽的终点:type 的类型仍然是 type


三、元类的基本写法

元类通常继承 type

class Meta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        print(f"creating {name}")
        return super().__new__(mcls, name, bases, namespace, **kwargs)


class User(metaclass=Meta):
    role = "guest"

运行时会输出:

creating User

这里的参数含义是:

  • mcls:当前元类,通常是 Meta
  • name:待创建类的名称,例如 "User"
  • bases:直接基类元组,例如 ()(Base,)
  • namespace:类主体执行后形成的命名空间;
  • kwargs:类定义行中传给元类的额外关键字参数。

Meta.__new__() 的职责是创建并返回类对象。调用 super().__new__() 可以把实际的类对象创建工作交给 type.__new__()

也可以覆盖元类的 __init__()

class Meta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        print(f"__new__: {name}")
        return super().__new__(mcls, name, bases, namespace, **kwargs)

    def __init__(cls, name, bases, namespace, **kwargs):
        print(f"__init__: {name}")
        super().__init__(name, bases, namespace, **kwargs)


class User(metaclass=Meta):
    pass

典型输出:

__new__: User
__init__: User

需要注意:这里的 __new__()__init__() 是元类的方法,所以它们处理的是“类对象的创建”,不是 User 实例的创建。


四、完整的类创建流程

考虑下面的代码:

class Meta(type):
    pass


class Base(metaclass=Meta):
    pass


class Child(Base):
    pass

Child 的创建过程可以拆成以下步骤。

sequenceDiagram
    participant P as Python 类定义机制
    participant M as Meta
    participant N as 类命名空间
    participant C as Child 类对象
    participant D as 装饰器

    P->>P: 解析基类和 MRO 条目
    P->>M: 确定适用元类
    P->>M: __prepare__("Child", (Base,), **kw)
    M-->>N: 返回 namespace
    P->>N: 执行 class Child 主体
    N-->>P: 得到类主体命名空间
    P->>M: Meta("Child", (Base,), namespace, **kw)
    M->>M: __new__()
    M->>M: __init__()
    M-->>C: 返回 Child 类对象
    P->>C: 执行 __set_name__ 和 __init_subclass__
    P->>D: 应用类装饰器
    D-->>P: 将最终对象绑定到 Child

规范层面的主要顺序是:

  1. 解析 MRO 条目;
  2. 确定适用的元类;
  3. 调用 __prepare__() 准备类命名空间;
  4. 执行类主体;
  5. 调用元类创建类对象;
  6. 执行与类创建相关的后续钩子;
  7. 应用类装饰器;
  8. 把最终结果绑定到类名。(docs.python.org)

下面分别分析每一步。


五、第一步:解析 MRO 条目

类定义中的基类表达式不一定直接返回一个类。

Python 允许一个对象通过 __mro_entries__() 参与基类解析:

class Base:
    pass


class BaseProvider:
    def __mro_entries__(self, original_bases):
        print("resolving MRO entry")
        return (Base,)


provider = BaseProvider()


class Child(provider):
    pass

执行时,provider 不是类,但它提供了 __mro_entries__(),因此 Python 可以把:

class Child(provider):
    pass

转换为等价的基类:

class Child(Base):
    pass

这个阶段发生在选择元类之前。原因是元类选择需要知道最终的基类集合;如果基类中含有可替换的 MRO 条目,就必须先解析它们。

当类定义中发生了 MRO 条目替换时,类命名空间中还可能包含 __orig_bases__,用于保留原始基类表达式。这类机制常见于泛型工具和代理基类。


六、第二步:确定适用的元类

元类不是简单地取“第一个基类的元类”。

假设:

class MetaA(type):
    pass


class MetaB(type):
    pass


class A(metaclass=MetaA):
    pass


class B(metaclass=MetaB):
    pass

下面的定义会失败:

class C(A, B):
    pass

因为 C 的元类必须同时兼容 MetaAMetaB,但二者互不继承。

错误通常类似:

TypeError: metaclass conflict:
the metaclass of a derived class must be a
(sub)class of the metaclasses of all its bases

形式化地说,设:

  • E 是显式指定的元类,如果没有则忽略;
  • M_i = type(B_i) 是每个基类 B_i 的元类;
  • M 是待选择的最终元类。

则需要找到一个候选元类 M,满足:

M 是 E 的子类(若 E 存在)
M 是每个 M_i 的子类

如果不存在这样的元类,类定义就失败。官方将该元类称为候选元类中的“最派生元类”。(docs.python.org)

可以通过让一个元类继承另一个元类来消除冲突:

class MetaA(type):
    pass


class MetaB(MetaA):
    pass


class A(metaclass=MetaA):
    pass


class B(metaclass=MetaB):
    pass


class C(A, B):
    pass

此时 MetaBMetaA 的子类,因此它能够满足两个基类的元类约束。

常见误解:元类冲突不是普通 MRO 冲突

下面两个问题不同:

  • 普通继承中的方法查找顺序冲突;
  • 类对象创建时的元类兼容性冲突。

前者属于类的 MRO 和 C3 线性化问题;后者发生在“连类对象都还没有创建出来”之前。即使普通基类的 MRO 可以计算成功,元类仍然可能不兼容。


七、第三步:__prepare__() 准备类命名空间

如果元类提供了 __prepare__(),Python 会先调用:

namespace = metaclass.__prepare__(name, bases, **kwargs)

官方要求这个方法应当实现为类方法。它返回的对象需要作为类主体执行期间的命名空间,并且随后会传给元类的 __new__();最终创建类对象时,通常会把该命名空间复制到一个新的 dict 中。(docs.python.org)

最小示例:

class Meta(type):
    @classmethod
    def __prepare__(mcls, name, bases, **kwargs):
        print("__prepare__", name, bases, kwargs)
        return {}

    def __new__(mcls, name, bases, namespace, **kwargs):
        print("__new__", name, list(namespace))
        return super().__new__(mcls, name, bases, namespace, **kwargs)


class User(metaclass=Meta, category="model"):
    identifier = 1

    def save(self):
        pass

输出类似:

__prepare__ User () {'category': 'model'}
__new__ User ['__module__', '__qualname__', 'identifier', 'save']

__prepare__() 发生在类主体执行之前,因此它能影响类主体中赋值语句的接收方式。

__prepare__() 的真实作用

它不是“返回最终的 __dict__”,而是返回一个临时的、用于执行类主体的映射对象。

类主体中的代码:

class User:
    a = 1
    b = 2

在概念上近似于:

namespace["a"] = 1
namespace["b"] = 2

因此,__prepare__() 可以用于:

  • 记录定义顺序;
  • 在定义阶段检测重复名称;
  • 收集特定类型的成员;
  • 对名称赋值施加额外约束;
  • 让类主体使用自定义命名空间语义。

Python 3.6 以后,普通类的属性定义顺序已经由字典保持;因此,“为了保持顺序”通常不再需要自定义 __prepare__()。类主体中属性定义的顺序在使用定义语法创建类时是可靠的,但这并不意味着所有动态构造类的方式都自动保留同样的来源顺序。(docs.python.org)


八、使用 __prepare__() 检测重复定义

下面的命名空间会拒绝同一个名称被定义两次:

class NoDuplicateDict(dict):
    def __setitem__(self, key, value):
        if key in self:
            raise TypeError(f"duplicate class name: {key}")
        super().__setitem__(key, value)


class StrictMeta(type):
    @classmethod
    def __prepare__(mcls, name, bases, **kwargs):
        return NoDuplicateDict()

    def __new__(mcls, name, bases, namespace, **kwargs):
        return super().__new__(mcls, name, bases, dict(namespace))


class Example(metaclass=StrictMeta):
    value = 1
    value = 2

类主体执行到第二个 value = 2 时就会失败:

TypeError: duplicate class name: value

这里的关键因果关系是:

  1. StrictMeta.__prepare__() 返回 NoDuplicateDict
  2. 类主体的赋值语句使用这个对象;
  3. 第二次写入 valueNoDuplicateDict.__setitem__() 拦截;
  4. 元类的 __new__() 根本不会执行。

这说明 __prepare__() 能发现并阻止一部分“类创建前错误”。

风险:不要随意限制所有名称

类主体不仅会写入业务成员,还可能写入:

__module__
__qualname__
__doc__
__annotations__
__classcell__

如果类中使用了零参数 super() 或显式引用 __class__,编译器还可能生成 __classcell__。因此,自定义命名空间如果错误地拒绝这些名称,可能导致正常类定义失败。


九、第四步:执行类主体

类主体不是声明式数据,而是会立即执行的代码:

print("before")


class Example:
    print("inside")
    value = 1


print("after")

输出:

before
inside
after

类主体会在新的执行框架中执行,使用准备好的类命名空间;执行结束后,执行框架被丢弃,但命名空间中的内容用于后续创建类对象。类主体可以引用当前和外部词法作用域中的名称,但类主体中的方法不能直接把类作用域当作普通闭包作用域使用。(docs.python.org)

例如:

def make_class():
    outer = "from outer"

    class Example:
        class_value = "from class"

        def method(self):
            return outer

    return Example

method() 可以读取外部函数作用域中的 outer

Example = make_class()
print(Example().method())

输出:

from outer

但下面的代码不能直接读取类主体中的 class_value

class Example:
    class_value = 10

    def method(self):
        return class_value

调用:

Example().method()

通常会产生:

NameError: name 'class_value' is not defined

应改为:

class Example:
    class_value = 10

    def method(self):
        return self.class_value

或:

class Example:
    class_value = 10

    @classmethod
    def method(cls):
        return cls.class_value

这与元类密切相关,因为元类接收的是“类主体执行后的命名空间”,而不是一个能够改变普通方法词法查找规则的环境。


十、__classcell__ 与零参数 super()

以下代码使用了零参数 super()

class Base:
    def method(self):
        return "Base"


class Child(Base):
    def method(self):
        return super().method() + " -> Child"

编译器需要为 Child.method 提供隐式的 __class__ 闭包引用。CPython 会把相应的 __classcell__ 条目放进传给元类的类命名空间中;如果自定义元类修改命名空间,却没有将它传递给 type.__new__(),可能在 Python 3.8 及以后触发 RuntimeError。这是 CPython 实现细节,但对编写元类具有实际影响。(docs.python.org)

错误示例:

class BrokenMeta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        namespace = {
            key: value
            for key, value in namespace.items()
            if key != "__classcell__"
        }
        return type.__new__(mcls, name, bases, namespace, **kwargs)


class Base:
    def method(self):
        return "Base"


class Child(Base, metaclass=BrokenMeta):
    def method(self):
        return super().method()

更安全的写法是原地修改,或者保留全部特殊条目:

class SafeMeta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        namespace["generated"] = True
        return super().__new__(mcls, name, bases, namespace, **kwargs)

如果必须构造新的字典,也要保留 __classcell__

class SafeMeta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        new_namespace = dict(namespace)
        new_namespace["generated"] = True
        return super().__new__(mcls, name, bases, new_namespace, **kwargs)

这里不要把 __classcell__ 当作普通业务属性删除。它是编译器和类创建机制之间的连接点。


十一、第五步:元类创建类对象

类主体执行结束后,Python 会调用:

metaclass(name, bases, namespace, **kwargs)

对于继承自 type 的元类,这通常会进入元类的 __call__(),再由它调用元类的 __new__()__init__()

可以通过覆盖元类的 __call__() 观察这个过程:

class Meta(type):
    def __call__(mcls, name, bases, namespace, **kwargs):
        print("Meta.__call__")
        cls = super().__call__(name, bases, namespace, **kwargs)
        print("class object created:", cls.__name__)
        return cls

    def __new__(mcls, name, bases, namespace, **kwargs):
        print("Meta.__new__")
        return super().__new__(mcls, name, bases, namespace, **kwargs)

    def __init__(cls, name, bases, namespace, **kwargs):
        print("Meta.__init__")
        super().__init__(name, bases, namespace, **kwargs)


class Example(metaclass=Meta):
    pass

典型输出:

Meta.__call__
Meta.__new__
Meta.__init__
class object created: Example

从职责上看:

  • 元类 __call__():控制一次“创建类对象”的完整调用;
  • 元类 __new__():决定类对象是否创建、创建成什么对象;
  • 元类 __init__():对已经创建好的类对象做初始化。

大多数元类只需要覆盖 __new__()__init__(),不需要覆盖 __call__()。覆盖 __call__() 会改变所有使用该元类创建类对象的路径,影响面更大。


十二、__set_name__():描述符获得自己的属性名

当元类最终通过 type.__new__() 创建类对象时,type.__new__() 会扫描类命名空间中定义了 __set_name__() 的对象,并调用:

descriptor.__set_name__(owner, name)

也就是说,描述符可以在类创建时知道自己被赋予了什么名称。(docs.python.org)

class Field:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner=None):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value


class User:
    name = Field()
    age = Field()

此时:

print(User.name.name)

输出:

name

创建实例:

user = User()
user.name = "Alice"
user.age = 30

print(user.name)
print(user.age)

输出:

Alice
30

这个过程与 ORM 字段、配置字段、验证属性等设计有关:

类主体:
    name = Field()

类创建:
    Field.__set_name__(User, "name")

实例访问:
    user.name
        ↓
    Field.__get__(user, User)

动态赋值不会自动调用 __set_name__()

class User:
    pass


field = Field()
User.name = field

这不会自动执行:

field.__set_name__(User, "name")

如果动态添加描述符,必须显式调用:

field.__set_name__(User, "name")

官方明确区分了类创建期间的自动回调和类创建之后的动态赋值。(docs.python.org)


十三、__init_subclass__():基类接收子类创建事件

创建子类时,父类可以通过 __init_subclass__() 自动接收通知:

class Base:
    def __init_subclass__(cls, **kwargs):
        print("subclass:", cls.__name__)
        super().__init_subclass__(**kwargs)


class Child(Base):
    pass

输出:

subclass: Child

__init_subclass__() 适合让一个基类约束或配置它的直接子类:

class Plugin:
    registry = {}

    def __init_subclass__(cls, *, name=None, **kwargs):
        super().__init_subclass__(**kwargs)

        plugin_name = name or cls.__name__.lower()
        if plugin_name in cls.registry:
            raise ValueError(f"duplicate plugin name: {plugin_name}")

        cls.registry[plugin_name] = cls


class JsonPlugin(Plugin, name="json"):
    pass


class XmlPlugin(Plugin, name="xml"):
    pass


print(Plugin.registry)

输出类似:

{'json': <class '__main__.JsonPlugin'>,
 'xml': <class '__main__.XmlPlugin'>}

与元类相比,__init_subclass__() 的作用范围更窄:它是基类的子类初始化钩子,不需要把整个类对象创建流程集中到一个元类中。

类定义行中的额外关键字会参与元类操作;但名为 metaclass 的关键字会被类创建机制消费,不会传给 __init_subclass__()。此外,协作式的 __init_subclass__() 应使用 super() 继续传递尚未处理的关键字。(docs.python.org)

例如:

class Base:
    def __init_subclass__(cls, *, enabled=True, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.enabled = enabled


class Feature(Base, enabled=False):
    pass

十四、__set_name__()__init_subclass__() 和元类的顺序

下面的代码可以观察几个钩子的相对顺序:

class Descriptor:
    def __set_name__(self, owner, name):
        print("__set_name__", owner.__name__, name)


class Base:
    def __init_subclass__(cls, **kwargs):
        print("__init_subclass__", cls.__name__)
        super().__init_subclass__(**kwargs)


class Meta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        print("Meta.__new__ before")
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        print("Meta.__new__ after")
        return cls

    def __init__(cls, name, bases, namespace, **kwargs):
        print("Meta.__init__")
        super().__init__(name, bases, namespace, **kwargs)


class Child(Base, metaclass=Meta):
    field = Descriptor()

典型输出为:

Meta.__new__ before
__set_name__ Child field
__init_subclass__ Child
Meta.__new__ after
Meta.__init__

这说明:

  1. __set_name__()__init_subclass__() 发生在 type.__new__() 的类创建过程中;
  2. 它们发生在元类 __new__()super().__new__() 返回之前;
  3. 元类 __init__() 在类对象创建完成后执行。

这里的具体调用细节依赖于是否调用了 type.__new__();如果元类完全绕过标准实现,标准钩子不一定自动发生。因此,自定义元类如果希望保留普通类的语义,通常应调用 super().__new__()


十五、类装饰器与元类不是一回事

考虑:

def decorate(cls):
    print("decorate", cls.__name__)
    cls.decorated = True
    return cls


class Meta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        print("Meta.__new__", name)
        return super().__new__(mcls, name, bases, namespace, **kwargs)


@decorate
class Example(metaclass=Meta):
    pass

输出顺序通常是:

Meta.__new__ Example
decorate Example

类装饰器大致等价于:

class Example(metaclass=Meta):
    pass

Example = decorate(Example)

类装饰器接收的是已经创建好的类对象,并且装饰器返回的对象最终绑定到类名。元类则参与类对象本身的创建。(docs.python.org)

区别可以概括为:

机制 发生时间 主要作用
__prepare__() 类主体执行前 准备类主体使用的命名空间
元类 __new__() 类主体执行后 创建或改写类对象
元类 __init__() 类对象创建后 初始化类对象
__set_name__() type.__new__() 期间 通知描述符所属类和属性名
__init_subclass__() 创建子类时 让父类配置或校验子类
类装饰器 类对象创建后 替换或修改最终绑定对象

如果需求只是“创建类后增加一个属性”,类装饰器往往比元类更直接:

def add_marker(cls):
    cls.marker = True
    return cls

只有当需求需要影响类主体执行前的命名空间、统一控制整个继承体系,或者必须参与所有子类的创建时,元类才更有必要。


十六、注册表:元类最常见的实际用途之一

注册表(registry)是把名称映射到类对象的字典:

registry = {
    "json": JsonSerializer,
    "xml": XmlSerializer,
}

之后可以根据配置名称取得实现类:

serializer_cls = registry["json"]
serializer = serializer_cls()

元类可以在类创建时自动完成注册。

基础实现

class RegisteredMeta(type):
    registry = {}

    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)

        register_as = namespace.get("register_as")
        if register_as is not None:
            if register_as in mcls.registry:
                old = mcls.registry[register_as]
                raise ValueError(
                    f"{register_as!r} already registered by {old.__name__}"
                )
            mcls.registry[register_as] = cls

        return cls


class Serializer(metaclass=RegisteredMeta):
    register_as = None


class JsonSerializer(Serializer):
    register_as = "json"

    def dumps(self, value):
        return f"json:{value}"


class XmlSerializer(Serializer):
    register_as = "xml"

    def dumps(self, value):
        return f"xml:{value}"

使用:

serializer_cls = RegisteredMeta.registry["json"]
print(serializer_cls("unused") if False else serializer_cls().dumps({"x": 1}))

输出:

json:{'x': 1}

更实际的工厂函数可以写成:

def make_serializer(name):
    try:
        cls = RegisteredMeta.registry[name]
    except KeyError as exc:
        raise LookupError(f"unknown serializer: {name}") from exc
    return cls()

为什么注册应在类对象创建之后进行

错误做法是只根据类主体命名空间注册:

class RegisteredMeta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        mcls.registry[name] = namespace
        return super().__new__(mcls, name, bases, namespace)

这样保存的是原始命名空间,不是最终类对象。它可能缺少或无法正确反映:

  • 继承关系;
  • 元类对类对象的修改;
  • __set_name__() 的结果;
  • __init_subclass__() 的结果;
  • 最终的类身份。

更合理的流程是:

namespace
    ↓
type.__new__()
    ↓
完整类对象
    ↓
注册类对象

也就是说,先让标准类创建流程完成,再把返回的类对象放进注册表。

注册基类本身还是只注册具体子类

通常不希望把抽象基类注册进去:

class RegisteredMeta(type):
    registry = {}

    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)

        if not namespace.get("__abstract__", False):
            key = namespace.get("register_as")
            if key is not None:
                mcls.registry[key] = cls

        return cls


class Handler(metaclass=RegisteredMeta):
    __abstract__ = True


class CreateHandler(Handler):
    register_as = "create"

但这种约定只是应用层协议,不是 Python 元类机制自动理解的抽象性。生产代码中应明确规定:

  • 注册键来自哪里;
  • 重复键如何处理;
  • 是否允许覆盖;
  • 导入模块失败时是否注册;
  • 测试之间如何清空注册表;
  • 多进程或多解释器环境是否需要独立注册表。

十七、注册表的导入时机和故障路径

元类注册依赖“类定义语句被执行”。

因此,下面的类不会自动注册:

class Plugin(metaclass=RegisteredMeta):
    register_as = "plugin"

如果包含它的模块从未被导入,类定义就没有执行,注册表自然为空。

典型故障路径如下:

配置读取 "json"
    ↓
查询 registry["json"]
    ↓
KeyError
    ↑
实现模块未导入,或类定义期间注册失败

诊断时应区分:

  1. 模块是否真正导入;
  2. 类定义是否执行;
  3. 元类 __new__() 是否抛出异常;
  4. 注册键是否重复;
  5. 注册表是否被测试或重载逻辑清空;
  6. 使用的是否是正确的元类注册表。

可以加入明确的诊断信息:

class RegisteredMeta(type):
    registry = {}

    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)

        key = namespace.get("register_as")
        if key:
            print(f"register {key!r} -> {cls.__module__}.{cls.__qualname__}")
            mcls.registry[key] = cls

        return cls

这类日志应放在“类对象创建成功之后、注册动作发生时”,否则可能把尚未成功创建的类误认为已经可用。


十八、并发下的注册问题

在单线程、启动阶段一次性导入模块的场景中,简单字典注册通常足够。

但如果注册发生在运行期,并且多个线程可能同时创建类,就要考虑复合操作:

if key in registry:
    raise ValueError("duplicate")
registry[key] = cls

这包含“检查”和“写入”两个步骤。即使单次字典操作在某些 Python 实现中具有原子性,也不应把它等同于完整的并发协议。

可以使用锁保护注册流程:

from threading import RLock


class RegisteredMeta(type):
    registry = {}
    _lock = RLock()

    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        key = namespace.get("register_as")

        if key is not None:
            with mcls._lock:
                if key in mcls.registry:
                    raise ValueError(f"duplicate key: {key}")
                mcls.registry[key] = cls

        return cls

但是锁不能解决所有问题:

  • 另一个线程可能在类注册前查询;
  • 模块导入本身可能处于部分初始化状态;
  • 多进程之间不共享普通进程内字典;
  • 多解释器环境也不能默认共享同一注册表;
  • 类重载可能造成旧类对象仍被注册表引用。

因此,运行期动态注册需要额外定义生命周期,而不能只依赖元类的一次性回调。


十九、元类中的 __new__() 如何改写类

元类可以在创建类对象前检查或改写命名空间:

class RequireIdMeta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        if name != "Entity" and "id" not in namespace:
            raise TypeError(f"{name} must define id")

        return super().__new__(mcls, name, bases, namespace, **kwargs)


class Entity(metaclass=RequireIdMeta):
    pass


class User(Entity):
    id = 0

下面则会失败:

class Broken(Entity):
    pass

错误:

TypeError: Broken must define id

这种校验的特点是失败发生在模块导入期间。如果类所在模块是应用启动必须导入的模块,错误会很早暴露;如果模块是插件动态加载的模块,则错误可能在运行期才出现。

改写方法也很容易破坏原始语义:

class RenameMeta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        if "run" in namespace:
            namespace["execute"] = namespace.pop("run")

        return super().__new__(mcls, name, bases, namespace, **kwargs)

这里不仅改变了属性名,还改变了:

  • __qualname__ 相关调试体验;
  • 文档工具看到的成员;
  • 子类对原名称的覆盖关系;
  • 依赖 run 的装饰器或描述符;
  • super() 相关的协作调用语义。

元类改写应尽量保持局部、可预测,并且明确记录哪些名称会被转换。


二十、元类与 MRO、super() 的关系

元类控制类对象的创建,但不取代普通类的 MRO。

例如:

class A:
    def process(self):
        return ["A"]


class B(A):
    def process(self):
        result = super().process()
        result.append("B")
        return result


class C(B):
    def process(self):
        result = super().process()
        result.append("C")
        return result

调用:

print(C().process())

输出:

['A', 'B', 'C']

零参数 super() 依赖类体编译时生成的隐式 __class__ 引用;类创建过程中,元类必须正确把相关的 __classcell__ 传给 type.__new__()。因此,元类虽然不负责计算每次方法调用的 MRO,但错误地破坏类创建流程,可能间接破坏 super()。(docs.python.org)

多继承中的协作方法要求每个实现都调用 super()

class LoggingMixin:
    def process(self):
        print("logging")
        return super().process()


class ValidationMixin:
    def process(self):
        print("validation")
        return super().process()


class Base:
    def process(self):
        print("base")


class Service(LoggingMixin, ValidationMixin, Base):
    pass


print(Service.__mro__)
Service().process()

输出类似:

(<class '__main__.Service'>,
 <class '__main__.LoggingMixin'>,
 <class '__main__.ValidationMixin'>,
 <class '__main__.Base'>,
 <class 'object'>)
logging
validation
base

如果元类动态插入基类、重写方法或改动类层次,就可能改变 MRO,进而改变 super() 的调用路径。元类因此不应把继承结构当作普通字典任意重写。


二十一、元类与描述符、property 的关系

描述符通过定义以下方法参与属性访问:

__get__
__set__
__delete__

property 本身就是一种描述符:

class User:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

元类可以在类创建时扫描描述符:

class DescriptorMeta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        descriptors = {
            key: value
            for key, value in namespace.items()
            if hasattr(value, "__get__")
        }

        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        cls.__descriptors__ = descriptors
        return cls


class User(metaclass=DescriptorMeta):
    @property
    def name(self):
        return "Alice"

但要注意时机。

如果在调用 super().__new__() 之前扫描原始命名空间,看到的是类主体直接赋予的对象;如果需要依赖 __set_name__() 已经产生的状态,应在标准类创建过程完成后再读取相关结果。

例如:

class Field:
    def __set_name__(self, owner, name):
        self.public_name = name
        self.storage_name = "_" + name


class Meta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)

        cls.fields = {
            key: value
            for key, value in cls.__dict__.items()
            if isinstance(value, Field)
        }
        return cls


class User(metaclass=Meta):
    name = Field()

此时:

print(User.fields["name"].storage_name)

输出:

_name

先创建类对象,再读取字段状态,才能确保 __set_name__() 已经完成。


二十二、元类关键字的传递规则

类定义可以携带额外关键字:

class Meta(type):
    def __new__(mcls, name, bases, namespace, *, table=None, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        cls.table = table
        return cls


class User(metaclass=Meta, table="users"):
    pass

此时 table="users" 会参与元类相关操作。

如果继承层次还使用了 __init_subclass__(),就必须明确哪些关键字由谁消费:

class Base:
    def __init_subclass__(cls, *, category=None, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.category = category


class Meta(type):
    def __new__(mcls, name, bases, namespace, *, table=None, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        cls.table = table
        return cls


class User(Base, metaclass=Meta, table="users", category="account"):
    pass

这里:

  • table 由元类消费;
  • categoryBase.__init_subclass__() 消费;
  • 其余未知关键字继续传给 super()

如果元类不接收某个关键字,或者没有在协作链中正确传递,类定义会失败。一个常见错误是元类“吃掉”所有关键字,却让基类钩子失去配置参数;另一个错误是基类钩子把已经消费过的关键字再次传给 object.__init_subclass__(),导致错误。


二十三、注册不一定需要元类

注册子类最简单的实现通常是 __init_subclass__()

class Plugin:
    registry = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        key = getattr(cls, "register_as", None)
        if key is not None:
            cls.registry[key] = cls


class JsonPlugin(Plugin):
    register_as = "json"

也可以使用类装饰器:

registry = {}


def register(name):
    def decorator(cls):
        registry[name] = cls
        return cls
    return decorator


@register("json")
class JsonPlugin:
    pass

三者的选择边界大致如下。

使用类装饰器

适合:

  • 只有少数类需要注册;
  • 注册规则显式比自动发现更重要;
  • 不希望改变整个继承体系;
  • 注册是一个局部模块行为。

使用 __init_subclass__()

适合:

  • 一个基类需要自动接收所有子类;
  • 规则与某个继承体系绑定;
  • 不需要改变类主体执行前的命名空间;
  • 希望多个基类通过 super() 协作。

使用元类

适合:

  • 必须在类主体执行前提供特殊命名空间;
  • 需要统一改写或验证类对象;
  • 需要控制多个相关基类的共同类创建行为;
  • 框架本身已经定义了明确的元类协议。

如果只为了“把子类放入字典”,元类通常不是唯一方案,也往往不是最窄的方案。


二十四、元类的继承与组合边界

元类本身也有继承关系:

class LoggingMeta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        print("creating", name)
        return super().__new__(mcls, name, bases, namespace, **kwargs)


class RegistryMeta(type):
    registry = {}

    def __new__(mcls, name, bases, namespace, **kwargs):
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)
        key = namespace.get("register_as")
        if key:
            mcls.registry[key] = cls
        return cls

如果某个类同时需要这两个元类的行为,可以组合元类:

class CombinedMeta(LoggingMeta, RegistryMeta):
    pass

然后:

class Plugin(metaclass=CombinedMeta):
    pass


class JsonPlugin(Plugin):
    register_as = "json"

但这要求两个元类都采用协作式写法:

return super().__new__(mcls, name, bases, namespace, **kwargs)

如果其中一个元类直接调用:

return type.__new__(mcls, name, bases, namespace)

就可能绕过另一个元类的逻辑。

这与普通多继承中的协作调用相同:每一层都必须正确调用 super(),并保持兼容的参数协议。元类多继承不是简单地把两个实现拼在一起,而是同样依赖一条可协作的 MRO 调用链。


二十五、使用边界:为什么元类容易造成系统耦合

元类会让类定义产生隐式行为:

class User(Model):
    name = StringField()

表面上只是定义属性,但背后可能执行:

  • 字段收集;
  • 名称校验;
  • SQL 表映射;
  • 注册;
  • 方法改写;
  • 子类钩子;
  • 元类初始化;
  • 描述符初始化。

这会带来几个具体边界。

1. 导入即执行

类定义通常在模块导入期间执行。元类抛出的错误可能导致整个模块无法导入。

2. 类定义不再是局部语义

阅读子类时,必须同时查看:

  • 所有父类;
  • 父类的元类;
  • 元类的 __prepare__()
  • 元类的 __new__()__init__()
  • __init_subclass__()
  • 描述符的 __set_name__()
  • 类装饰器。

3. 第三方基类可能发生冲突

继承两个外部框架基类时,它们各自的元类可能不兼容:

class Combined(FrameworkA, FrameworkB):
    pass

这时不能只修改 Combined 的类体;需要分析两个框架元类的继承关系,并可能编写组合元类。

4. 动态类生成更难排查

以下写法也会触发元类:

Dynamic = Meta("Dynamic", (Base,), {"value": 1})

但它没有源码中的自然类主体。若系统依赖定义顺序、模块名、限定名或导入路径,动态创建类需要额外验证这些元数据。

5. 元类与序列化、代理、代理类交互复杂

某些库会动态创建包装类、代理类或缓存类。如果元类把每个新类都注册到全局表中,可能导致:

  • 临时类泄漏;
  • 重复注册;
  • 测试之间状态污染;
  • 代理类被误识别为业务类;
  • 缓存持有类对象,阻止模块重新加载后的旧类回收。

二十六、如何诊断元类问题

遇到元类相关错误,可以按类创建流程逆向排查。

1. 检查实际元类

print(type(SomeClass))
print(SomeClass.__class__)

二者都表示 SomeClass 的元类。

2. 检查基类元类

for base in SomeClass.__bases__:
    print(base, type(base))

若类定义失败,则直接检查参与继承的基类:

print(type(BaseA))
print(type(BaseB))

3. 检查 MRO

print(SomeClass.__mro__)

如果是多继承,确认方法调用顺序和预期一致。

4. 检查类主体是否执行

print("before class")


class Example(metaclass=Meta):
    print("inside class")


print("after class")

如果没有看到 inside class,错误可能发生在基类表达式或元类确定阶段;如果看到了 inside class 但没有看到元类日志,错误可能发生在类主体结束后的元类调用路径。

5. 检查命名空间特殊项

class InspectMeta(type):
    def __new__(mcls, name, bases, namespace, **kwargs):
        print(list(namespace))
        print("__classcell__" in namespace)
        return super().__new__(mcls, name, bases, namespace, **kwargs)

特别关注:

__module__
__qualname__
__doc__
__annotations__
__classcell__

6. 检查注册表中的类是否为最终类对象

registered = RegisteredMeta.registry["json"]

print(registered)
print(type(registered))
print(registered.__module__)
print(registered.__qualname__)

如果注册表保存的是命名空间、函数或代理对象,而预期是类对象,注册时机或装饰器返回值可能有问题。


二十七、一个完整的可运行示例

下面实现一个小型命令处理框架,包含:

  • 元类注册;
  • 重复名称检测;
  • 抽象基类排除;
  • __init_subclass__() 配置;
  • 描述符 __set_name__()
  • 工厂函数和错误处理。
from __future__ import annotations

from typing import Any


class Field:
    def __init__(self, default: Any = None):
        self.default = default
        self.name: str | None = None
        self.storage_name: str | None = None

    def __set_name__(self, owner: type, name: str) -> None:
        self.name = name
        self.storage_name = f"_{name}"

    def __get__(self, instance: Any, owner: type | None = None) -> Any:
        if instance is None:
            return self

        assert self.storage_name is not None
        return getattr(instance, self.storage_name, self.default)

    def __set__(self, instance: Any, value: Any) -> None:
        assert self.storage_name is not None
        setattr(instance, self.storage_name, value)


class CommandMeta(type):
    registry: dict[str, type["Command"]] = {}

    @classmethod
    def __prepare__(
        mcls,
        name: str,
        bases: tuple[type, ...],
        **kwargs: Any,
    ) -> dict[str, Any]:
        return {}

    def __new__(
        mcls,
        name: str,
        bases: tuple[type, ...],
        namespace: dict[str, Any],
        **kwargs: Any,
    ) -> type:
        cls = super().__new__(mcls, name, bases, namespace, **kwargs)

        command_name = namespace.get("command_name")
        is_abstract = namespace.get("abstract", False)

        if command_name is not None and not is_abstract:
            if command_name in mcls.registry:
                old = mcls.registry[command_name]
                raise TypeError(
                    f"duplicate command {command_name!r}: "
                    f"{old.__qualname__} already registered"
                )

            mcls.registry[command_name] = cls

        return cls


class Command(metaclass=CommandMeta):
    abstract = True
    command_name: str | None = None

    def __init_subclass__(cls, *, version: int = 1, **kwargs: Any) -> None:
        super().__init_subclass__(**kwargs)
        cls.version = version

    request_id = Field()


class CreateUser(Command, command_name="create-user", version=2):
    abstract = False

    def run(self) -> str:
        return f"create user, request_id={self.request_id}"


class DeleteUser(Command, command_name="delete-user"):
    abstract = False

    def run(self) -> str:
        return f"delete user, request_id={self.request_id}"


def execute(command_name: str, request_id: str) -> str:
    try:
        command_cls = CommandMeta.registry[command_name]
    except KeyError as exc:
        raise LookupError(
            f"unknown command: {command_name!r}; "
            f"available={sorted(CommandMeta.registry)}"
        ) from exc

    command = command_cls()
    command.request_id = request_id
    return command.run()


print(sorted(CommandMeta.registry))
print(CreateUser.version)
print(execute("create-user", "req-001"))

预期输出:

['create-user', 'delete-user']
2
create user, request_id=req-001

这个示例的生命周期是:

定义 Command
    ↓
创建 Command 类对象
    ↓
发现 abstract=True,不注册

定义 CreateUser
    ↓
执行类主体,得到 command_name、request_id、run
    ↓
type.__new__()
    ↓
Field.__set_name__(CreateUser, "request_id")
    ↓
Command.__init_subclass__(version=2)
    ↓
CommandMeta.__new__() 将 CreateUser 注册
    ↓
execute() 根据名称取出 CreateUser
    ↓
CreateUser() 创建实例
    ↓
实例属性访问经过 Field 描述符

这个例子也说明了不同机制的分工:

  • Field 负责实例属性访问;
  • __set_name__() 负责发现字段名;
  • __init_subclass__() 负责读取子类配置;
  • CommandMeta 负责将最终类对象放入全局注册表;
  • 工厂函数负责运行期错误处理。

如果只需要注册子类,可以把元类注册逻辑移到 Command.__init_subclass__(),这样系统会更简单。这里使用元类,是为了展示“创建类对象之后注册最终类对象”的完整流程。


二十八、规范保证、实现细节与经验建议

规范保证

以下属于 Python 数据模型明确描述的类创建机制:

  • 类主体会在新的命名空间中执行;
  • 可以通过 metaclass 指定元类;
  • Python 会确定适用的最派生元类;
  • 元类可以提供 __prepare__()
  • 类主体执行结果会传给元类;
  • type.__new__() 会处理 __set_name__()
  • 子类创建会触发 __init_subclass__()
  • 类装饰器在类对象创建后应用;
  • 类定义中的属性顺序会被保留在定义语法创建的类命名空间中。(docs.python.org)

CPython 实现细节

以下内容需要标注实现边界:

  • __classcell__ 作为类命名空间条目传给元类,是 CPython 文档明确说明的实现细节;
  • 自定义元类删除或丢失 __classcell__ 可能破坏零参数 super()
  • 某些对象布局、缓存和解释器级调用细节不应当仅凭 CPython 行为推断为所有 Python 实现都必须相同。(docs.python.org)

经验建议

经验层面的取舍是:

  • 需要修改实例行为时,优先考虑普通方法、描述符或 property
  • 需要对所有子类执行配置时,优先考虑 __init_subclass__()
  • 需要局部注册时,优先考虑类装饰器;
  • 需要类主体执行前的自定义命名空间,才考虑 __prepare__()
  • 需要统一控制一整个类层次的创建,才考虑元类;
  • 自定义元类通常应调用 super(),保留 type.__new__()__set_name__()__init_subclass__()__classcell__ 等标准路径;
  • 注册表应保存最终类对象,并明确导入、重复键、并发、重载和清理策略。

元类的核心不是“让 Python 更动态”,而是把类定义本身变成可拦截、可验证、可改写的运行时流程。理解了命名空间准备、类主体执行、元类选择、类对象创建和后续钩子的顺序,才能判断一个需求到底需要元类,还是普通继承、描述符、__init_subclass__() 或装饰器已经足够。


系列导航与关联阅读

官方资料

本文依据 Python 官方文档、相关 PEP 与生态项目官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。