Python 基础体系 · 第 29/112 篇。示例统一以 Python 3.14 为语言基线;第三方库使用与其兼容的现代稳定版本,版本敏感行为会单独说明。
Python 元类:类创建流程、prepare、注册和使用边界
元类(metaclass)是“创建类的类”。
普通对象由类创建:
class User:
pass
user = User()
这里,user 是 User 的实例:
type(user) is User
而 User 自身也是一个对象,它的类型通常是 type:
type(User) is type
因此可以把关系写成:
user ──实例化──> User ──实例化──> type
元类改变的是第二条边:它控制“类对象如何被创建”。它通常不负责普通实例的初始化,而是负责类定义语句执行结束后,如何把类名、基类和类命名空间组装成一个新的类对象。
Python 官方将类创建过程分为几个阶段:解析 MRO 条目、确定适用的元类、准备类命名空间、执行类主体、创建类对象;之后还可能执行 __set_name__()、__init_subclass__() 和类装饰器。(docs.python.org)
一、先区分三个不同的“创建”
理解元类之前,必须把以下三个过程分开:
- 执行
class语句; - 创建类对象;
- 使用类对象创建实例。
例如:
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
规范层面的主要顺序是:
- 解析 MRO 条目;
- 确定适用的元类;
- 调用
__prepare__()准备类命名空间; - 执行类主体;
- 调用元类创建类对象;
- 执行与类创建相关的后续钩子;
- 应用类装饰器;
- 把最终结果绑定到类名。(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 的元类必须同时兼容 MetaA 和 MetaB,但二者互不继承。
错误通常类似:
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
此时 MetaB 是 MetaA 的子类,因此它能够满足两个基类的元类约束。
常见误解:元类冲突不是普通 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
这里的关键因果关系是:
StrictMeta.__prepare__()返回NoDuplicateDict;- 类主体的赋值语句使用这个对象;
- 第二次写入
value被NoDuplicateDict.__setitem__()拦截; - 元类的
__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__
这说明:
__set_name__()和__init_subclass__()发生在type.__new__()的类创建过程中;- 它们发生在元类
__new__()从super().__new__()返回之前; - 元类
__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
↑
实现模块未导入,或类定义期间注册失败
诊断时应区分:
- 模块是否真正导入;
- 类定义是否执行;
- 元类
__new__()是否抛出异常; - 注册键是否重复;
- 注册表是否被测试或重载逻辑清空;
- 使用的是否是正确的元类注册表。
可以加入明确的诊断信息:
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由元类消费;category由Base.__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 完整学习路线:从语言模型、并发到 Web、数据、AI 与生产交付
- 上一篇:Python 特殊方法:容器、运算符、调用、表示与上下文协议
- 下一篇:Python 反射与自省:inspect、签名、Frame、AST 和安全边界
- 延伸:Python MRO 与 super:C3 线性化、多继承和协作调用
- 延伸:Python 描述符与 property:属性访问、绑定方法和 ORM 基础
官方资料
本文依据 Python 官方文档、相关 PEP 与生态项目官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。

评论
0 条讨论