当前位置:首页>python>Python 元类(下):进阶与实战建议

Python 元类(下):进阶与实战建议

  • 2026-10-11 07:37:09
Python 元类(下):进阶与实战建议

上篇我们理解了 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__
控制类怎么被创建(改命名空间、拦截属性)
元类
拦截实例化(单例、对象池)
元类重写 __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 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 上包了一层,拦截了类的创建过程。

仅此而已。

最新文章

随机文章