前置知识: Python

元类

2 minAdvanced2026/6/14

元类与类创建过程

概述

(Metaclass)是 Python 中用于创建。普通创建实例对象,而元创建对象。理解元需要先理解 Python 的创建机制:本身也是对象,是 type 的实例。元允许我们在创建时拦截和修改的定义过程,实现诸如自动注册、字段验证、接口检查等高级功能。

基础概念

也是对象

在 Python 中,本身也是对象。class 语句本质上是一个语法糖,它调用 type 来创建

# 使用 class 关键字
class MyClass:
    x = 10

# 等价于使用 type 动态创建
MyClass = type('MyClass', (), {'x': 10})

type 的三个参数:

  • 名(字符串)
  • 元组
  • 属性/方法字典

的层次

type 创建 MyClass
MyClass 创建 my_instance

type 是 MyClass 的元类
type 是 type 自身的元类(自举)

的触发时机

定义被解析时调用,而不是在实例化时调用。这意味着元的修改在创建时就生效了。

快速上手

自定义元

class Meta(type):
    """自定义元类"""
    def __new__(mcs, name, bases, namespace):
        # 修改类创建过程
        namespace['class_id'] = id(mcs)
        return super().__new__(mcs, name, bases, namespace)

class MyClass(metaclass=Meta):
    pass

obj = MyClass()
print(obj.class_id)  # 自动添加了 class_id 属性

new vs init vs call

有三个可以重写的方法:

class Meta(type):
    def __new__(mcs, name, bases, namespace):
        """创建类对象(最先调用)"""
        print(f"Meta.__new__ 创建类: {name}")
        return super().__new__(mcs, name, bases, namespace)

    def __init__(cls, name, bases, namespace):
        """初始化类对象(__new__ 之后调用)"""
        print(f"Meta.__init__ 初始化类: {name}")
        super().__init__(name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        """控制实例创建过程"""
        print(f"Meta.__call__ 创建实例: {cls.__name__}")
        return super().__call__(*args, **kwargs)

class MyClass(metaclass=Meta):
    pass

# 输出:
# Meta.__new__ 创建类: MyClass
# Meta.__init__ 初始化类: MyClass

obj = MyClass()  # 输出: Meta.__call__ 创建实例: MyClass

详细用法

自动注册模式

class RegistryMeta(type):
    """自动注册子类的元类"""
    _registry = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        # 不注册基类本身
        if bases and bases[0] is not object:
            mcs._registry[name.lower()] = cls
        return cls

class Plugin(metaclass=RegistryMeta):
    """插件基类"""
    def execute(self):
        raise NotImplementedError

class MySQLPlugin(Plugin):
    def execute(self):
        return "MySQL 执行"

class RedisPlugin(Plugin):
    def execute(self):
        return "Redis 执行"

# 自动注册
print(RegistryMeta._registry)
# {'mysqlplugin': <class 'MySQLPlugin'>, 'redisplugin': <class 'RedisPlugin'>}

字段验证

class ValidatedMeta(type):
    """验证类属性的元类"""
    def __new__(mcs, name, bases, namespace):
        # 验证所有公开属性必须是简单类型
        for key, value in namespace.items():
            if key.startswith('_'):
                continue
            if not isinstance(value, (int, str, float, bool)):
                raise TypeError(f"{key} 必须是简单类型,实际是 {type(value).__name__}")
        return super().__new__(mcs, name, bases, namespace)

class Config(metaclass=ValidatedMeta):
    host = "localhost"
    port = 8080
    debug = False
    # tags = []  # TypeError: tags 必须是简单类型,实际是 list

单例元

class Singleton(type):
    """单例元类"""
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=Singleton):
    def __init__(self):
        print("初始化数据库连接")
        self.connection = "connected"

# 多次实例化返回同一对象
db1 = Database()  # 初始化数据库连接
db2 = Database()  # 不会再次初始化
print(db1 is db2)  # True

接口检查

class InterfaceMeta(type):
    """接口元类:检查子类是否实现了所有抽象方法"""
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)

        # 基类本身不需要检查
        if not bases or bases == (object,):
            return cls

        # 收集所有需要实现的抽象方法
        abstract_methods = set()
        for base in bases:
            abstract_methods.update(getattr(base, '_abstract_methods', set()))

        # 检查是否全部实现
        for method in abstract_methods:
            if method not in namespace:
                raise TypeError(f"{name} 未实现抽象方法: {method}")

        return cls

class Animal(metaclass=InterfaceMeta):
    _abstract_methods = {'speak', 'move'}

class Dog(Animal):
    def speak(self):
        return "汪汪"

    def move(self):
        return "跑"

# class Cat(Animal):  # TypeError: Cat 未实现抽象方法: speak, move
#     pass

常见场景

场景一:ORM 字段映射

class Field:
    """数据库字段描述符"""
    def __init__(self, field_type, column_name=None):
        self.field_type = field_type
        self.column_name = column_name

    def __set_name__(self, owner, name):
        if self.column_name is None:
            self.column_name = name

class ModelMeta(type):
    """ORM 模型元类"""
    def __new__(mcs, name, bases, namespace):
        fields = {}
        for key, value in namespace.items():
            if isinstance(value, Field):
                fields[key] = value
        namespace['_fields'] = fields
        return super().__new__(mcs, name, bases, namespace)

class Model(metaclass=ModelMeta):
    pass

class User(Model):
    id = Field(int, 'user_id')
    name = Field(str)
    age = Field(int)

print(User._fields)  # {'id': Field(...), 'name': Field(...), 'age': Field(...)}

场景二:方法自动绑定

class EventMeta(type):
    """自动注册事件处理器的元类"""
    def __new__(mcs, name, bases, namespace):
        handlers = {}
        for key, value in namespace.items():
            if key.startswith('on_'):
                event_name = key[3:]  # 去掉 on_ 前缀
                handlers[event_name] = value
        namespace['_handlers'] = handlers
        return super().__new__(mcs, name, bases, namespace)

class EventHandler(metaclass=EventMeta):
    def dispatch(self, event_name, *args, **kwargs):
        handler = self._handlers.get(event_name)
        if handler:
            return handler(self, *args, **kwargs)

class MyHandler(EventHandler):
    def on_click(self, button):
        print(f"点击了: {button}")

    def on_keypress(self, key):
        print(f"按下了: {key}")

h = MyHandler()
h.dispatch("click", "提交")  # 点击了: 提交

注意事项

  • 代码复杂,容易出错,应优先考虑其他方案(装饰器init_subclass装饰器
  • Python 之禅:“元是 99% 的程序员都不需要关心的深奥魔法”
  • 多个元冲突时需要解决 MRO(方法解析顺序)问题
  • 会影响所有子,修改时需要考虑继承链上的影响
  • 调试元代码比较困难,因为创建发生在导入时

进阶用法

init_subclass 替代简单元

Python 3.6 引入的 __init_subclass__ 可以替代许多简单的元场景:

class Plugin:
    """使用 __init_subclass__ 替代元类"""
    _registry = {}

    def __init_subclass__(cls, name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        Plugin._registry[name or cls.__name__] = cls

class MySQLPlugin(Plugin, name="mysql"):
    pass

class RedisPlugin(Plugin, name="redis"):
    pass

print(Plugin._registry)  # {'mysql': <class 'MySQLPlugin'>, 'redis': <class 'RedisPlugin'>}

装饰器替代元

def add_repr(cls):
    """类装饰器:自动添加 __repr__"""
    def __repr__(self):
        attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@add_repr
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

print(Point(1, 2))  # Point(x=1, y=2)

与描述符配合

class Typed:
    """类型检查描述符"""
    def __init__(self, expected_type):
        self.expected_type = expected_type

    def __set_name__(self, owner, name):
        self.name = f"_{name}"

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.name, None)

    def __set__(self, obj, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(f"期望 {self.expected_type.__name__},得到 {type(value).__name__}")
        setattr(obj, self.name, value)

class StrictMeta(type):
    """严格类型检查元类"""
    def __new__(mcs, name, bases, namespace):
        # 将类型注解转换为 Typed 描述符
        annotations = namespace.get('__annotations__', {})
        for attr_name, attr_type in annotations.items():
            if attr_name not in namespace:  # 没有默认值
                namespace[attr_name] = Typed(attr_type)
        return super().__new__(mcs, name, bases, namespace)

class Person(metaclass=StrictMeta):
    name: str
    age: int

p = Person()
p.name = "Alice"  # OK
# p.name = 42     # TypeError: 期望 str,得到 int