0Pricing
Python Academy · 课时

编写自定义元类

在元类中使用 __new__ 和 __init__ 自定义类的创建过程。

编写自定义元类 是 CoddyKit 上的免费 Python Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Python Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Python Academy 课程共包含 4 节课。

声明元类

在类定义中传入 metaclass=MyMeta,即可使用自定义元类。该元类必须继承自 type。

class MyMeta(type):
    pass

class Foo(metaclass=MyMeta):
    pass

print(type(Foo))   # <class '__main__.MyMeta'>

重写 __new__

__new__(mcs, name, bases, namespace) 创建类对象。可以使用它在构建类之前修改命名空间。

class UpperAttrMeta(type):
    def __new__(mcs, name, bases, namespace):
        upper = {k.upper(): v for k, v in namespace.items()
                 if not k.startswith("_")}
        return super().__new__(mcs, name, bases, {**namespace, **upper})

class Config(metaclass=UpperAttrMeta):
    debug = True

print(Config.DEBUG)  # True

重写 __init__

__init__(cls, name, bases, namespace) 在类创建后运行。可以使用它执行创建后的设置,例如注册。

_registry = {}

class AutoRegisterMeta(type):
    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        if bases:   # skip the base class itself
            _registry[name] = cls

class Handler(metaclass=AutoRegisterMeta): pass
class JSONHandler(Handler): pass
class XMLHandler(Handler):  pass

print(_registry)  # {'JSONHandler': ..., 'XMLHandler': ...}

强制执行接口契约

如果类体中缺少必需的方法,请在 __new__ 中引发 TypeError。

class InterfaceMeta(type):
    REQUIRED = frozenset({"process", "validate"})

    def __new__(mcs, name, bases, ns):
        if bases and not mcs.REQUIRED.issubset(ns):
            missing = mcs.REQUIRED - ns.keys()
            raise TypeError(f"{name} must define: {missing}")
        return super().__new__(mcs, name, bases, ns)

class Worker(metaclass=InterfaceMeta):
    def process(self): ...
    def validate(self): ...

通过元类实现 Singleton

重写元类上的 __call__,拦截实例创建,并始终返回同一个实例。

class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *a, **kw):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*a, **kw)
        return cls._instances[cls]

class DB(metaclass=SingletonMeta):
    pass

print(DB() is DB())  # True

动态添加方法

将实用方法注入每个使用该元类的类,而无需逐个修改这些类。

class MethodInjectorMeta(type):
    def __new__(mcs, name, bases, ns):
        ns["describe"] = lambda self: f"I am {type(self).__name__}"
        return super().__new__(mcs, name, bases, ns)

class Widget(metaclass=MethodInjectorMeta):
    pass

print(Widget().describe())   # I am Widget

用于有序命名空间的 __prepare__

重写 __prepare__,为类命名空间提供自定义映射,例如保留定义顺序或检测重复项。

class NoDuplicatesMeta(type):
    class _NS(dict):
        def __setitem__(self, k, v):
            if k in self and not k.startswith("_"):
                raise ValueError(f"Duplicate attribute: {k}")
            super().__setitem__(k, v)

    @classmethod
    def __prepare__(mcs, name, bases):
        return mcs._NS()

元类 conflict 解决

当多个基类具有不同的元类时,Python 要求显式使用派生程度最高的元类,否则会引发冲突错误。

class MetaA(type): pass
class MetaB(type): pass

# class Bad(A, B): pass  # TypeError: metaclass conflict

class MetaAB(MetaA, MetaB): pass   # resolve manually

class A(metaclass=MetaA): pass
class B(metaclass=MetaB): pass
class Good(A, B, metaclass=MetaAB): pass

元类与 __init_subclass__

对于子类注册和简单钩子,__init_subclass__ 更简单,并且不需要元类。

class Base:
    _registry = []
    def __init_subclass__(cls, **kw):
        super().__init_subclass__(**kw)
        Base._registry.append(cls)

class ChildA(Base): pass
class ChildB(Base): pass

print(Base._registry)  # [ChildA, ChildB]

跟踪属性访问

在元类上重写 __getattribute__,可以拦截对类(而不是实例)的属性访问。

class TraceMeta(type):
    def __getattribute__(cls, name):
        val = super().__getattribute__(name)
        if not name.startswith("_"):
            print(f"Accessing class attr: {name}")
        return val

class Config(metaclass=TraceMeta):
    HOST = "localhost"

_ = Config.HOST   # logs: Accessing class attr: HOST

ORM 字段收集模式

Django 风格的 ORM 使用元类,将类体中的 Field 描述符收集到 _meta 注册表中。

class Field:
    def __init__(self, col): self.col = col

class ModelMeta(type):
    def __new__(mcs, name, bases, ns):
        fields = {k: v for k, v in ns.items() if isinstance(v, Field)}
        ns["_fields"] = fields
        return super().__new__(mcs, name, bases, ns)

class User(metaclass=ModelMeta):
    name = Field("name")
    email = Field("email")

print(User._fields.keys())  # dict_keys(['name', 'email'])

快速检查

在自定义元类中,在 __init__ 之前调用哪个方法来创建类对象本身?

回顾

自定义元类会重写 __new__/__init__ 来控制类的创建:强制执行契约、自动注册子类、注入方法或收集字段描述符。对于简单的注册需求,请优先使用 __init_subclass__。

常见问题解答

「编写自定义元类」课时是免费的吗?

是的 — 「编写自定义元类」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Python Academy 课程的其余内容,请升级到 CoddyKit PRO。 Python Academy 课程共包含 4 节课。

「编写自定义元类」这节课中我会学到什么?

在元类中使用 __new__ 和 __init__ 自定义类的创建过程。 你通过在浏览器中直接运行的动手代码来练习 Python Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Python Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Python Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「编写自定义元类」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Python Academy 课中编写并运行代码吗?

能。每节 Python Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Python 类的创建方式
  2. 编写自定义元类
  3. 描述器:__get__、__set__、__delete__
  4. __slots__ 与内存优化
← 返回 Python Academy