上篇我们理解了 type 是什么,中篇动手写了几个元类。这一篇,我们把剩下的能力补齐,然后聊一个更重要的问题——什么时候该用,什么时候不该用。
在类体执行之前,先准备好「画布」
中篇我们重写了 __new__,它在类体执行之后拿到 namespace 字典。但 Python 3 还给了你一个更早的钩子——__prepare__,它在类体执行之前就被调用。
from collections import OrderedDictclass OrderedMeta(type): @classmethod def __prepare__(mcs, name, bases): print(" 准备命名空间...") return OrderedDict() def __new__(mcs, name, bases, namespace): print(" 创建类...") cls = super().__new__(mcs, name, bases, namespace) cls._field_order = [k for k in namespace if not k.startswith('__')] return clsclass User(metaclass=OrderedMeta): name = "" age = 0 email = ""# 输出:# 准备命名空间...# 创建类...print(User._field_order) # ['name', 'age', 'email']
整个过程是这样的:
__prepare__() ← 返回一个空字典,作为类体的「画布」 │ ▼执行类体代码 ← name = ""、age = 0 这些写入字典 │ ▼__new__() ← 拿到填好的字典,创建类对象
默认情况下 __prepare__ 返回一个普通 dict,但你可以换成任何映射类型——比如 OrderedDict 来保留顺序,或者一个自定义字典类来拦截赋值操作。
小知识:Python 3.7+ 的普通 dict 已经保证插入顺序了(这是 CPython 3.6 的实现细节,3.7 起成为语言规范),所以纯顺序场景下 __prepare__ 意义减弱。但它的价值在于——你可以返回一个自定义映射类型,在每次赋值时做一些额外的事情,比如类型检查或日志记录。
让 isinstance 说谎
isinstance() 通常是看继承关系的。但元类可以重写 __instancecheck__,让它不按套路出牌:
class PositiveMeta(type): def __instancecheck__(cls, instance): # 不看继承关系,只看值 return isinstance(instance, (int, float)) and instance > 0class PositiveNumber(metaclass=PositiveMeta): passprint(isinstance(42, PositiveNumber)) # Trueprint(isinstance(-5, PositiveNumber)) # Falseprint(isinstance(3.14, PositiveNumber)) # Trueprint(isinstance(0, PositiveNumber)) # Falseprint(isinstance("hello", PositiveNumber)) # False
PositiveNumber 没有任何实例,也没有继承 int 或 float。但 isinstance 被劫持了——它现在只做值判断。
这看起来像是奇技淫巧,但在某些场景下很有用。Python 的 collections.abc 模块就是用类似的手法来实现「协议检查」的——比如任何实现了 __iter__ 的对象,isinstance(obj, Iterable) 就返回 True,不需要真的继承 Iterable。
元类也会继承
子类会自动继承父类的元类,不需要你显式指定:
class BaseMeta(type): def __new__(mcs, name, bases, namespace): cls = super().__new__(mcs, name, bases, namespace) cls._made_by = mcs.__name__ return clsclass X(metaclass=BaseMeta): passclass Y(X): pass # 没写 metaclass=,但自动继承了 BaseMetaprint(X._made_by) # BaseMetaprint(Y._made_by) # BaseMeta ← 继承过来了print(type(Y) is BaseMeta) # True
这个行为和普通类继承是一致的——你没定义 __init__,子类就用父类的。元类也一样。
但元类会冲突
如果两个父类用了不同的元类,而且这两个元类没有继承关系,Python 就不知道该听谁的了:
class MetaA(type): passclass MetaB(type): passclass A(metaclass=MetaA): passclass B(metaclass=MetaB): passtry: class C(A, B): # A 用 MetaA, B 用 MetaB → 冲突 passexcept TypeError as e: print(e) # metaclass conflict: the metaclass of a derived class must be a # (non-strict) subclass of the metaclasses of all its bases
Python 官方文档对这条规则的表述是:派生类的元类必须是所有基类元类的(非严格)子类。
解决方法很直白:让一个元类继承另一个,制造出兼容关系:
class MetaC(MetaA): # MetaC 是 MetaA 的子类,兼容了 passclass D(metaclass=MetaC): passclass E(A, D): # A 用 MetaA, D 用 MetaC(MetaA 的子类)→ 兼容 passprint(type(E)) # <class 'MetaC'> —— 自动选了更具体的那个
Python 3 还给了你一个更简单的选择
说实话,很多场景下你根本不需要元类。
Python 3.6 引入了 __init_subclass__,它能做元类最常见的事情——在子类创建时执行一段逻辑——但写法简单得多:
class PluginBase: registry = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) PluginBase.registry.append(cls.__name__) print(f" 发现子类: {cls.__name__}")class Auth(PluginBase): pass # 输出: 发现子类: Authclass Logger(PluginBase): pass # 输出: 发现子类: Loggerprint(PluginBase.registry) # ['Auth', 'Logger']
没有 type 继承,没有 __new__,没有 metaclass=——就是一个普通的类方法。效果和中篇的插件注册表例子一模一样。
那什么时候该用哪个?
| |
|---|
| __init_subclass__ |
| |
| |
| |
| __getattr__ |
Tim Peters 说过:
「元类是比 99% 的用户需要担心的更深的魔法。如果你在犹豫是否需要元类,那你不需要。真正需要它的人,确定自己需要,不需要别人解释为什么。」
Real Python 的建议也类似:如果问题可以用更简单的方式解决,那大概就应该用更简单的方式。元类是「寻找问题的解决方案」的典型——听起来很酷,但大多数时候你并不需要。
__init_subclass__ 能解决的,就别用元类。元类是核武器——威力大,维护成本也大。只有当你需要控制 __new__ 或 __prepare__ 时,才真正需要它。
Python 2 vs Python 3 完整对照
到这里,把两代 Python 的差异做个总结:
| | |
|---|
| class Foo: | |
| | |
| __metaclass__ = X | class Foo(metaclass=X) |
__metaclass__ | | 被忽略 |
__prepare__ | | |
__init_subclass__ | | |
type() | | |
super() | super(ClassName, self) | super() |
迁移 Python 2 代码时最容易踩的坑:
# ❌ Python 2 写法 —— Python 3 中完全无效class MyClass: __metaclass__ = MyMeta# ✅ Python 3 写法class MyClass(metaclass=MyMeta): pass
注意:Python 3 不会报错,只是默默忽略 __metaclass__。这比报错更危险——你以为元类生效了,其实没有。
最后
元类的完整能力清单:
| | |
|---|
| __prepare__ | |
| __new__ | |
| __init__ | |
| __call__ | Foo() |
| __instancecheck__ | isinstance() |
绝大多数时候你用不到它们。但当你看到 Django、SQLAlchemy 这些框架的魔法代码时,你会知道它们在做什么——它们只是在 type 上包了一层,拦截了类的创建过程。
仅此而已。