码农高天
元类在类定义阶段通过__new__与__init__完成属性约束与类对象构建,并在调用类生成实例时由__call__统一接管创建逻辑。
AI-assisted summary of 【python】metaclass理解加入门,看完就知道什么是元类了。 — original source. Check the original for full context.
Source published:
Key ideas
- 元类(Metaclass)是类的“制造者”,声明 `class A(metaclass=M)` 并非继承,而是指示 Python 使用 `M` 代替默认的 `type` 来构建该类对象。 (0:00)
- 元类的 `__new__` 与 `__init__` 在类定义阶段立即执行,可用于对类属性进行强行校验、拦截或动态修改,解决普通继承无法覆盖的元编程场景。 (0:00)
- 元类的 `__call__` 函数在类实例化(如 `A()`)时触发,能直接控制对象的产生过程,是实现无侵入式单例模式的核心机制。 (5:00)
- 在元类中通过 `super()` 调用父类方法存在参数陷阱:`__init__` 与 `__call__` 需剔除显式的 `self`/`cls` 参数,而 `__new__` 则必须保留。 (5:00)
Detailed notes
## Background 该视频由 YouTube 频道“码农高天”于 2024 年 1 月 25 日发布。简介指出,由于元类(metaclass)本身概念偏晦涩,本期视频主要从使用者角度带大家入门、理解和认识元类的基本用法,未来有机会再深入介绍其底层机制。
## TL;DR 视频从 Python 中动态生成类的底层逻辑出发,系统阐述了元类的本质是用来替代默认的 type 来构建类对象的机制。高天深入梳理了元类的核心生命周期方法:__new__ 与 __init__ 在类定义声明阶段即被隐式触发,可用于在类构建期进行属性拦截与约束检查;而元类的 __call__ 则在类被实例化时触发,常用于实现单例模式等控制实例生成的逻辑。此外,视频还特别指出了在元类中使用 super() 调用时 __new__ 与 __init__、__call__ 传参规范不一致的常见开发陷阱。
## Atmosphere & style 视频风格务实且通俗易懂,高天以标志性的清晰条理和循序渐进的代码实验,将原本高深抽象的 Python 元编程机制拆解得直观透彻。整个讲解兼具启发性与警示性,既有揭开底层黑盒时豁然开朗的满足感,又在提醒 super() 传参大坑时展现出资深开发者的严谨与敏锐。
## Bulletin - [00:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=0s) 元类(Metaclass)是类的“制造者”,声明 `class A(metaclass=M)` 并非继承,而是指示 Python 使用 `M` 代替默认的 `type` 来构建该类对象。 - [00:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=0s) 元类的 `__new__` 与 `__init__` 在类定义阶段立即执行,可用于对类属性进行强行校验、拦截或动态修改,解决普通继承无法覆盖的元编程场景。 - [05:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=300s) 元类的 `__call__` 函数在类实例化(如 `A()`)时触发,能直接控制对象的产生过程,是实现无侵入式单例模式的核心机制。 - [05:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=300s) 在元类中通过 `super()` 调用父类方法存在参数陷阱:`__init__` 与 `__call__` 需剔除显式的 `self`/`cls` 参数,而 `__new__` 则必须保留。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=0s) Python 中“一切皆对象”,类本身也是一个对象,默认情况下静态类定义等价于调用 `type(name, bases, attrs)` 动态创建类。 - [00:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=0s) 通过在类声明中指定 `metaclass=M`,可替代底层直通 C 函数的默认 `type` 行为,将类对象变为元类 `M` 的实例。 - [00:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=0s) 元类的 `__new__` 方法在 Python 解析并定义类时即刻触发,无需创建任何类的实例即可执行。 - [00:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=0s) 借助元类的 `__new__` 可以在类定义时对类属性字典进行审查,例如通过检测方法名前缀来强制推行架构规范,不合规则即刻抛错中断加载。 - [05:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=300s) 元类的 `__init__` 紧随其 `__new__` 之后在类定义阶段执行,此时类对象已经构建完毕,适合对其直接进行属性扩展与加工。 - [05:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=300s) 极易混淆的元类 `__call__` 并不在类定义时运行,而是在类被调用以创建实例(如执行 `o = A()`)时触发。 - [05:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=300s) 元类 `__call__` 的典型应用场景是单例模式(Singleton),通过拦截实例创建流程返回缓存对象,避免污染业务类内部逻辑。 - [05:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=300s) 在元类中使用 `super()` 语法时需注意传参差异:`__new__` 必须保留首位的类参数,而 `__init__` 与 `__call__` 则需要将其去除。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=0s) 元类(Metaclass)的基本概念与调用时机
元类(Metaclass)在 Python 中是一个公认比较晦涩且涉及知识点极多的话题。从使用者的角度来看,我们可以通过对比静态类定义与动态类生成来理解它。通常情况下,我们静态定义一个 `class A:`,其底层本质与调用 `type(name, bases, attrs)` 动态生成类是完全等价的,都是向 `type` 传入类名、父类元组以及属性字典这三个参数。由于直接使用 `type` 会直接下沉调用到底层的 C 函数中,如果我们希望在类创建的中间环节插入自定义逻辑,就需要借助元类来实现类似 `A = M("A", bases, attrs)` 的效果。
当我们在类定义中写下 `class A(metaclass=M):` 时,并不代表 `A` 继承了 `M`(继承的语法是 `class A(M):`),而是向 Python 明确指示:“不要再用默认的 `type` 来给我建立类了,请使用 `M` 来给我建立这个类。”在 Python 中,“一切皆对象”,类本身也是一个对象。此时,类 `A` 是元类 `M` 的一个实例对象(instance),而后续通过 `o = A()` 实例化出的 `o` 则是类 `A` 的对象。
我们在执行 `o = A()` 时会隐式调用类 `A` 的 `__new__` 和 `__init__` 函数;同理,当定义 `class A(metaclass=M):` 时,Python 也会在幕后隐式调用元类 `M` 的 `__new__` 和 `__init__` 函数,并传入相应的三个参数。高天通过在 `class M(type)` 的 `__new__` 方法中加入打印 `name`、`bases` 和 `dict` 的语句来验证这一点:代码中仅仅声明了 `class A`,并没有使用 `A` 创建任何实例,终端就已经打印出了对应的参数信息。这证明了元类的 `__new__` 函数是在“定义类”那一瞬间被执行的,开发者可以借此在类构建阶段插入自定义逻辑。
“在 class A 的 definition 里面加了一个 metaclass 等于 M 的意思就是,你不要再用 type 来给我建立 class 了,你用这个 M 来给我建立 class。”
```mermaid flowchart TD accTitle: Python元类与类及实例的实例化关系 accDescr: 展示元类M、类A与实例o之间的两层实例化机制及调用方法
subgraph MetaclassLevel["元类层 (Metaclass)"] M["元类 M (继承自 type)"] end
subgraph ClassLevel["类定义阶段 (Class Definition)"] A["类 A<br/>class A(metaclass=M)"] end
subgraph InstanceLevel["实例阶段 (Instantiation)"] O["实例 o<br/>o = A()"] end
M -->|"类定义时隐式调用<br/>M.__new__ / M.__init__"| A A -->|"实例化时隐式调用<br/>M.__call__ -> A.__new__ / A.__init__"| O ```
- 元类是用来解决普通类继承(Inheritance)所无法解决的问题的机制。 - 适用场景示例:对类的属性或方法名进行强约束。比如规定类中不能出现以 `test_` 开头的函数,可以在元类的 `__new__` 中检查 `dict` 的所有键,若匹配到则直接 `raise Error`,这样在类定义的阶段就会直接报错拦截。 - 元类赋予了开发者在现有继承体系之外,根据成员定义动态增删或修改类属性与方法的自由度。
[05:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=300s) 元类的 `__init__` 与 `__call__` 函数及避坑指南
与 Python 中的其他对象一样,元类 `M` 同样可以拥有 `__init__` 函数。元类的 `__init__` 也发生在类定义被加载的时候,紧随 `__new__` 之后执行。二者最大的区别在于:当 `__init__` 执行时,代表类本身的那个对象(即 `A`,它是元类 `M` 的一个实例)已经构建完成了。此时可以直接对新建的类对象进行加工,例如在 `M` 的 `__init__` 中为 `self.random_id` 赋予一个 0 到 100 的随机数,之后访问 `A.random_id` 便能得到该值。虽然简单的属性赋值可以直接在类体中书写,但在面对需要元编程的复杂动态逻辑时,元类就显得不可或缺。
除了 `__new__` 和 `__init__`,元类中还可以重载 `__call__` 函数,这也是极易被混淆的地方。元类的 `__call__` 并不是在定义 `class A` 时执行的,而是在执行 `o = A()` 时被触发的。换言之,元类的 `__call__` 接管了该类尝试产生实例的过程。通常情况下,我们在元类的 `__call__` 中会直接调用父类(`type`)的 `__call__` 来完成实例的创建与初始化,但开发者可以在此之前插入拦截逻辑,典型的应用便是单例模式(Singleton):在 `__call__` 中检查该类是否已经存在实例化对象,如果存在就直接返回旧实例,若不存在再调用底层机制创建新实例。
在高阶语法使用上,高天特别提出了一个“大坑”作为避坑提醒:在元类的方法中,如果使用 `super()` 代替直接显式调用 `type` 上的方法(如 `type.__call__`),必须注意传参形式的差异。在元类的 `__init__` 和 `__call__` 中,调用 `super()` 时需要去掉开头的 `self`(即 `cls`)参数;但是在 `__new__` 的 `super()` 调用中,该参数是不能去掉的。关于该行为底层的 CPython 机制与难啃的原代码,高天表示未来有机会再深入填坑讲解。
“这个 call 函数并不是在定义 class A 的时候被调用的,而是在你做 A 括号的时候被调用的,也就是一般来说这个 call 函数应该是你新建的这个类在尝试产生实例的时候被使用的。”
- 元类生命周期划分: - `__new__`:定义类时调用,负责创建并返回代表该类的对象。 - `__init__`:定义类时紧接着 `__new__` 调用,用于对已生成的类对象进行初始化操作。 - `__call__`:类在被实例化(如 `A()`)时调用,控制实例的生成过程。 - 单例模式可在元类的 `__call__` 中低侵入性实现,免去污染具体业务类内部代码的麻烦。 - 元类方法使用 `super()` 调用的参数差异:`__init__` 与 `__call__` 需移除显式的 `self`/`cls` 参数,而 `__new__` 则必须保留。
## Notable quotes > [00:00](https://www.youtube.com/watch?v=E_-Nl3My3mo&t=0s) 在 class A 的 definition 里面加了一个 metaclass 等于 M 的意思就是,你不要再用 type 来给我建立 class 了,你用这个 M 来给我建立 class。 — 码农高天
## Mentioned Python, CPython, 码农高天
## Video description 大家期待已久的话题metaclass终于来啦!由于这个内容本身就偏晦涩,我们这期视频主要从一个使用者的角度先去理解和认识metaclass,未来如果有机会,我们再去介绍它背后的机制!
[Source](https://www.youtube.com/watch?v=E_-Nl3My3mo)