metaclass 的本质是类的构造器,用于在类创建时动态修改类定义,而非装饰或补丁已有类;它必须继承 type,通过 metaclass= 指定,常用 new 修改 namespace 或返回类对象。

metaclass 的本质是类的构造器,不是装饰器
元类不是用来“增强”已有类的工具,它是在 class 语句执行完毕、类对象真正创建出来之前介入的钩子。如果你试图用它去补丁一个已定义好的类(比如在模块加载后动态加方法),那走错了路——该用 setattr 或装饰器。metaclass 只对使用它的类定义本身生效,且仅在类创建时运行一次。
常见错误现象:TypeError: metaclass conflict —— 多个父类指定了不同元类,或显式写了 metaclass=type 却没继承 type;另一个是误以为给实例加属性能触发 __new__ 或 __init_subclass__,其实不会。
- 元类必须继承自
type(哪怕只是class MyMeta(type): pass) - 类定义中通过
metaclass=MyMeta指定,不能写成__metaclass__ = MyMeta(那是 Python 2 写法) -
__new__接收的是类名、基类元组、命名空间字典,返回的是新类对象;别在里面改cls(此时cls还不存在)
用 __new__ 动态注入方法和属性最直接
这是最常用也最可控的方式:在类创建过程中,修改传入的 namespace 字典,或在返回前给新建的类对象设置属性。注意顺序——namespace 是原始定义体,还没经过任何继承合并;而最终类对象上的操作会覆盖/补充它。
class AutoLogMeta(type):
def __new__(mcs, name, bases, namespace):
# 在 namespace 中插入方法(所有子类都会带这个方法)
def log(self):
print(f"[{name}] {self}")
namespace['log'] = log
<pre class="brush:php;toolbar:false;"> # 也可根据类名或注解决定是否添加
if 'auto_save' in namespace.get('__annotations__', {}):
namespace['save'] = lambda self: print("saved")
return super().__new__(mcs, name, bases, namespace)
立即学习“Python免费学习笔记(深入)”;
使用场景:统一添加序列化接口、自动注册类到全局表、强制要求实现某字段(如 __version__)、为 ORM 模型自动补全字段访问器。
- 不要在
__new__里调用super().__new__前就访问namespace中可能被父类覆盖的键(比如__init__),因为此时还未合并 - 如果需要基于继承链做判断,改用
__init_subclass__(但它是实例方法,不适用于元类级逻辑) - 动态添加的属性无法被 IDE 静态识别,需配类型存根或文档说明
__init_subclass__ 更轻量,适合子类定制而非类生成
如果你不需要干预类对象的构造过程(比如不改 MRO、不重写 __dict__、不替换 __call__),只是想让每个子类“知道”自己被创建了,并做些登记或校验,__init_subclass__ 是更安全、更现代的选择。它本质是 type.__new__ 内部自动调用的钩子,比手写元类简单得多。
class PluginBase:
plugins = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.plugins[cls.__name__] = cls
if not hasattr(cls, 'execute'):
raise TypeError(f"{cls.__name__} must implement execute()")
<p>class Exporter(PluginBase):
def execute(self):
return "exported"
性能影响很小,兼容性好(Python 3.6+),而且天然支持关键字参数传递(class X(PluginBase, priority=10):)。但它不能改变类的类型、不能替换 __new__ 实现、也不能阻止类被创建——出错只能抛异常中断。
- 别在
__init_subclass__里做耗时操作(如 I/O、网络请求),它在 import 时就执行 - 它对直接继承
object的类无效,只对明确继承了定义该方法的父类才触发 - 如果父类用了元类,
__init_subclass__仍会调用,但优先级低于元类__new__
绕不开的陷阱:元类 + 继承 + 多重继承时的冲突
当多个父类带有不同元类,或某个父类用了元类而另一个没指定,Python 会尝试“合并”它们。规则是:所有元类必须是同一类或其子类,否则报 metaclass conflict。最常见的踩坑点是混用 abc.ABCMeta 和自定义元类。
解决方式只有两种:要么让自定义元类继承 ABCMeta(如果确实需要抽象能力),要么用 ABCMeta 的子类作为基元类再扩展:
from abc import ABCMeta
<p>class ValidatedMeta(ABCMeta):
def <strong>new</strong>(mcs, name, bases, namespace):
if 'validate' not in namespace:
raise TypeError(f"{name} must define validate()")
return super().<strong>new</strong>(mcs, name, bases, namespace)</p><p>class Model(metaclass=ValidatedMeta, ABC=True): # 注意:ABC=True 是 abc.ABCMeta 的惯用法
pass
- 永远不要试图在运行时动态切换一个类的元类(比如
cls.__class__ = NewMeta),这会破坏内部结构 - 调试时打印
cls.__mro__和type(cls)能快速确认元类是否生效 - 绝大多数业务场景其实不需要元类——先考虑组合、协议(
Protocol)、数据类(@dataclass)或类装饰器
真正需要元类的地方很少,一旦用上,后续维护者大概率会盯着你的 __new__ 发呆十分钟。把逻辑拆到可测试的函数里,再在元类中调用,比堆砌元编程更可持续。


















