metaclass在类定义时起作用,其__new__方法在类对象创建前执行,接收类名、父类和命名空间(dict),可扫描、重写或注入属性;它不干预实例化过程,也不应与装饰器或__init__混淆。

metaclass 在类定义时就起作用,不是实例化时
元类的 __new__ 方法在类对象被创建出来前执行,此时类名、父类、命名空间(即类体中定义的所有内容)都已确定但尚未构造成真正的类。这意味着你能在类诞生那一刻就扫描、重写或注入属性,比如把所有下划线开头的变量自动转成只读、给每个方法加装饰器、或者统一注册类到某个全局表里。
常见错误是以为 metaclass 能修改实例行为——它不碰 __init__ 或实例属性;也有人把它和装饰器混淆,结果在 __init__ 里徒劳地试图“改类”,其实早没机会了。
-
__new__(metacls, name, bases, namespace)是核心入口,namespace是个 dict,包含类定义里的所有键值对(包括函数、变量、注解) - 修改
namespace后必须调用super().__new__(...),否则类根本造不出来 - 别在
namespace里直接删 key(比如del namespace['x']),某些 Python 版本会报TypeError: __new__() takes exactly 4 arguments;稳妥做法是构造新 dict 或用pop()+ 重建
如何安全地重命名或替换类属性
想把 _secret 改成 __secret,或把所有 field_* 属性自动包装成 property,就得在 namespace 中识别并替换。关键是不能破坏原有语义——比如方法对象本身不能被字符串替代,否则类定义会失败。
典型场景:ORM 模型中把字段声明(如 name = StringField())转换为描述符,同时保留原始名称用于后续反射。
- 遍历
namespace.items(),用isinstance(value, Field)这类判断识别目标属性 - 替换时推荐用新 dict 构建:
new_ns = {k: transform(v) if should_transform(k, v) else v for k, v in namespace.items()} - 避免覆盖
__module__、__qualname__等系统 key,它们由解释器控制,改了会导致 import 失败或调试信息错乱 - 如果要注入新属性(如
_fields = [...]),直接写进新 dict 即可,不用额外 setattr
metaclass 和 __set_name__ 的协作边界
当属性是描述符时,__set_name__ 会在类创建后、属性被赋值到类上时触发,时间点比 metaclass __new__ 稍晚。两者能配合,但职责分明:metaclass 负责“结构改造”,__set_name__ 负责“绑定上下文”。
容易踩的坑是试图在 metaclass 里手动调用 descr.__set_name__(cls, name)——这多余且危险,因为解释器会在合适时机自动调用;更糟的是,如果你提前调了,而该描述符又依赖 cls 的某些元信息(比如继承链),此时 cls 可能还没完全构建好。
- metaclass 适合做一次性静态改写:重命名、类型校验、添加类级标记
- 描述符的
__set_name__更适合动态绑定:记录所属类名、设置内部状态、生成默认值 - 二者叠加时,确保 metaclass 不干扰描述符的初始化逻辑(比如别把描述符实例替换成别的对象)
Python 3.12+ 的 __set_name__ 自动调用与兼容性注意
从 Python 3.12 开始,只要描述符实现了 __set_name__,解释器就会无条件调用它,不再要求属性必须出现在类定义中(即支持动态设置)。但这不影响 metaclass 的执行时机——它依然在类对象诞生前运行。
这意味着:如果你的 metaclass 修改了描述符实例(比如 deep copy 了一个字段对象),而这个副本没被重新绑定到类上,那它的 __set_name__ 就不会被调用,导致字段丢失所属类信息。
- 若需复制描述符,在 metaclass 中应确保复制后仍保留在
namespace中对应 key 下,且不破坏其 identity(例如不要用copy.copy()后又丢弃原引用) - 跨版本兼容时,别依赖
__set_name__是否被调用作为逻辑分支;优先在 metaclass 中完成必需的静态配置 - 调试时可用
print(f"Metaclass sees {list(namespace.keys())}")和print(f"Class has {list(cls.__dict__.keys())}")对比,确认属性是否真的落到了类上
__annotations__ 的处理——它也在 namespace 里,但属于特殊键,改错会导致类型检查器失效或 mypy 报错。还有人忘了元类本身也要继承自 type,写成普通类会导致 TypeError: metaclass must be a class。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











