上篇我们理解了 type 是什么,中篇动手写了几个元类。这一篇,我们把剩下的能力补齐,然后聊一个更重要的问题------什么时候该用,什么时候不该用。
在类体执行之前,先准备好「画布」
中篇我们重写了 __new__,它在类体执行之后 拿到 namespace 字典。但 Python 3 还给了你一个更早的钩子------__prepare__,它在类体执行之前就被调用。
python
from collections import OrderedDict
class 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 cls
class 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__,让它不按套路出牌:
python
class PositiveMeta(type):
def __instancecheck__(cls, instance):
# 不看继承关系,只看值
return isinstance(instance, (int, float)) and instance > 0
class PositiveNumber(metaclass=PositiveMeta):
pass
print(isinstance(42, PositiveNumber)) # True
print(isinstance(-5, PositiveNumber)) # False
print(isinstance(3.14, PositiveNumber)) # True
print(isinstance(0, PositiveNumber)) # False
print(isinstance("hello", PositiveNumber)) # False
PositiveNumber 没有任何实例,也没有继承 int 或 float。但 isinstance 被劫持了------它现在只做值判断。
这看起来像是奇技淫巧,但在某些场景下很有用。Python 的 collections.abc 模块就是用类似的手法来实现「协议检查」的------比如任何实现了 __iter__ 的对象,isinstance(obj, Iterable) 就返回 True,不需要真的继承 Iterable。
元类也会继承
子类会自动继承父类的元类,不需要你显式指定:
python
class BaseMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
cls._made_by = mcs.__name__
return cls
class X(metaclass=BaseMeta):
pass
class Y(X):
pass # 没写 metaclass=,但自动继承了 BaseMeta
print(X._made_by) # BaseMeta
print(Y._made_by) # BaseMeta ← 继承过来了
print(type(Y) is BaseMeta) # True
这个行为和普通类继承是一致的------你没定义 __init__,子类就用父类的。元类也一样。
但元类会冲突
如果两个父类用了不同的元类,而且这两个元类没有继承关系,Python 就不知道该听谁的了:
python
class MetaA(type):
pass
class MetaB(type):
pass
class A(metaclass=MetaA):
pass
class B(metaclass=MetaB):
pass
try:
class C(A, B): # A 用 MetaA, B 用 MetaB → 冲突
pass
except 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 官方文档对这条规则的表述是:派生类的元类必须是所有基类元类的(非严格)子类。
解决方法很直白:让一个元类继承另一个,制造出兼容关系:
python
class MetaC(MetaA): # MetaC 是 MetaA 的子类,兼容了
pass
class D(metaclass=MetaC):
pass
class E(A, D): # A 用 MetaA, D 用 MetaC(MetaA 的子类)→ 兼容
pass
print(type(E)) # <class 'MetaC'> ------ 自动选了更具体的那个
Python 3 还给了你一个更简单的选择
说实话,很多场景下你根本不需要元类。
Python 3.6 引入了 __init_subclass__,它能做元类最常见的事情------在子类创建时执行一段逻辑------但写法简单得多:
python
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 # 输出: 发现子类: Auth
class Logger(PluginBase):
pass # 输出: 发现子类: Logger
print(PluginBase.registry) # ['Auth', 'Logger']
没有 type 继承,没有 __new__,没有 metaclass=------就是一个普通的类方法。效果和中篇的插件注册表例子一模一样。
那什么时候该用哪个?
| 你想做的事 | 用什么 |
|---|---|
| 类创建之后做点什么(注册、注入默认值) | __init_subclass__ |
| 控制类怎么被创建(改命名空间、拦截属性) | 元类 |
| 拦截实例化(单例、对象池) | 元类重写 __call__ |
| 给类加方法、改方法 | 类装饰器 |
| 改实例的行为 | __getattr__、__setattr__ |
Tim Peters 说过:
「元类是比 99% 的用户需要担心的更深的魔法。如果你在犹豫是否需要元类,那你不需要。真正需要它的人,确定自己需要,不需要别人解释为什么。」
Real Python 的建议也类似:如果问题可以用更简单的方式解决,那大概就应该用更简单的方式。元类是「寻找问题的解决方案」的典型------听起来很酷,但大多数时候你并不需要。
__init_subclass__ 能解决的,就别用元类。元类是核武器------威力大,维护成本也大。只有当你需要控制 __new__ 或 __prepare__ 时,才真正需要它。
Python 2 vs Python 3 完整对照
到这里,把两代 Python 的差异做个总结:
| 特性 | Python 2 | Python 3 |
|---|---|---|
| 旧式类 | class Foo: 不继承 object |
不存在,所有类都是新式类 |
| 新式类 | 必须写 class Foo(object): |
默认就是 |
| 指定元类 | __metaclass__ = X(类变量) |
class Foo(metaclass=X) |
__metaclass__ 类变量 |
生效 | 被忽略 |
__prepare__ |
不支持 | 支持 |
__init_subclass__ |
不支持 | Python 3.6+ |
type() 和 __class__ |
旧式类不一致 | 始终一致 |
super() 写法 |
super(ClassName, self) |
super() |
迁移 Python 2 代码时最容易踩的坑:
python
# ❌ Python 2 写法 ------ Python 3 中完全无效
class MyClass:
__metaclass__ = MyMeta
# ✅ Python 3 写法
class MyClass(metaclass=MyMeta):
pass
注意:Python 3 不会报错,只是默默忽略 __metaclass__。这比报错更危险------你以为元类生效了,其实没有。
最后
元类的完整能力清单:
| 能力 | 方法 | 何时调用 |
|---|---|---|
| 控制命名空间 | __prepare__ |
类体执行前 |
| 创建类 | __new__ |
类体执行后 |
| 初始化类 | __init__ |
类创建完成后 |
| 拦截实例化 | __call__ |
Foo() 时 |
| 自定义 isinstance | __instancecheck__ |
isinstance() 调用时 |
绝大多数时候你用不到它们。但当你看到 Django、SQLAlchemy 这些框架的魔法代码时,你会知道它们在做什么------它们只是在 type 上包了一层,拦截了类的创建过程。
仅此而已。