CPython将class定义转为普通函数 核心机制等同动态type调用

【python】你知道定义class背后的机制和原理嘛?当你定义class的时候,python实际运行了什么呢? — 码农高天

码农高天

Python类声明本质上是在独立命名空间中执行代码体并收集局部变量,最终交由底层type构造器绑定为类属性,与运行时直接调用三参数type函数完全等价。

AI-assisted summary of 【python】你知道定义class背后的机制和原理嘛?当你定义class的时候,python实际运行了什么呢? — original source. Check the original for full context.

Source published:

Key ideas

  • Python 中的 class 定义在底层并非特殊黑盒,而是将类体包装为一个独立的 code object 并在临时命名空间中执行的可运行代码。 (0:00)
  • CPython 解释器通过 `LOAD_BUILD_CLASS` 调用 `builtins.__build_class__`,默认使用元类 `type` 来完成类对象的装配。 (5:00)
  • 类体内产生的所有局部变量会被写入临时字典,并最终拷贝绑定到底层 C 结构的 `tp_dict`(即 Python 层的 `__dict__`)中供后续属性查找。 (5:00)
  • 静态 `class` 语法与动态调用 `type(name, bases, dict)` 在底层完全等价,三参数 `type` 是 Python 设计中保留的历史特性。 (10:00)

Detailed notes

## Background 该视频由 YouTube 频道“码农高天”于 2023 年 10 月 30 日发布。视频简介指出,本期内容聚焦 Python 中定义 class 背后的底层运行机制,通过解析 CPython 源码说明类的定义本质上只是一段普通的可运行 Python 代码,破除面向对象语法的神秘感。

## TL;DR 视频首先通过字节码拆解了一个极简 Python 类的定义过程,展示类体代码如何被编译为独立的 code object 并通过内置的 __build_class__ 进行调用。随后深入 CPython 源码,详细剖析了 builtin___build_class__ 如何将类体作为局部环境执行、把生成的局部变量写入命名空间字典,并最终通过元类 type 的底层实现(type_call 与 type_new)将字典挂载为类的 tp_dict。最后,作者演示了使用动态三参数 type(name, bases, dict) 手动构建类的方法,阐明静态 class 语法与底层动态建类完全等价的本质。

## Atmosphere & style 视频风格硬核严谨却不失轻松通俗,呈现出极具个人特色的技术漫谈与源码研读氛围。高天以极简的代码示例为切入点,通过字节码和 CPython 源码的逐行追踪,将看似高深的解释器内部细节抽丝剥茧、层层拆解。整期内容充满“知其所以然”的解密快感与探究乐趣,既有干货满满的硬核分析,又带有编程老手化繁为简的亲和力与自信。

## Bulletin - [00:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=0s) Python 中的 class 定义在底层并非特殊黑盒,而是将类体包装为一个独立的 code object 并在临时命名空间中执行的可运行代码。 - [05:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=300s) CPython 解释器通过 `LOAD_BUILD_CLASS` 调用 `builtins.__build_class__`,默认使用元类 `type` 来完成类对象的装配。 - [05:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=300s) 类体内产生的所有局部变量会被写入临时字典,并最终拷贝绑定到底层 C 结构的 `tp_dict`(即 Python 层的 `__dict__`)中供后续属性查找。 - [10:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=600s) 静态 `class` 语法与动态调用 `type(name, bases, dict)` 在底层完全等价,三参数 `type` 是 Python 设计中保留的历史特性。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=0s) 类定义体内的成员方法编译出的 code object 与顶层定义的普通函数在底层字节码结构上没有任何区别。 - [00:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=0s) 类体会被编译为一个独立的 code object,执行时会隐式设置内置变量 `__module__` 与 `__qualname__`。 - [00:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=0s) 外层字节码通过 `LOAD_BUILD_CLASS` 加载 `builtins.__build_class__` 并传入类体函数和类名来启动类构建流程。 - [05:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=300s) 在未显式指定 metaclass 且无特殊基类的情况下,`__build_class__` 默认使用元类 `type`(C 源码中的 `&PyType_Type`)。 - [05:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=300s) 所有在 Python 中直接通过 `class` 语法定义的类对象,其类型本身全部都是 `type`。 - [05:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=300s) CPython 会初始化一个名为 `ns` 的空字典作为 `locals` 传入类体执行环境,类内的属性赋值与方法声明均直接写入该字典。 - [05:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=300s) 调用元类 `type` 会触发底层类型槽函数 `tp_call`,进而依次执行 `type_new` 和 `type_init` 完成对象的实例化。 - [05:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=300s) 底层的 `type_new` 函数将传入的局部变量字典拷贝并赋值给新建类型的 `tp_dict`,该字段即 Python 层通过 `__dict__` 访问的映射表。 - [10:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=600s) 类定义的本质是先执行类体代码收集所有局部变量,再将键值对存入 `__dict__` 并通过 `type` 构造类对象。 - [10:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=600s) 动态建类需要三个核心要素:类名字符串、基类元组以及包含属性和方法的命名空间字典。 - [10:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=600s) 使用 `type(name, bases, dict)` 动态建类与使用 `class` 语法定义的类在底层调用完全相同的 C 实现。 - [10:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=600s) 单参数 `type(obj)` 用于查询对象类型,三参数 `type(name, bases, dict)` 用于创建新类,这种双重含义属于 Python 的历史包袱。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=0s) 字节码层面的 class 定义:从内层函数到 `__build_class__`

在日常编写 Python 时,开发者几乎每天都在定义类,但 Python 解释器在背后到底执行了哪些操作,很多人并不清楚。码农高天在左侧屏幕展示了一段仅有四行代码的极简类定义:定义了一个名为 `A` 的类,包含一个成员变量 `name = 'AA'` 和一个打印 `1` 的成员方法 `f`。然而,这段简短的代码在 Python 解释器中所生成的字节码结构却并不简单。

```mermaid flowchart TD accTitle: Python 类定义执行流程 accDescr: 展示 Python 解释器从解析 class 内部代码到调用 build_class 并最终返回 type 的执行路径。

subgraph Phase1["1. 解析内部结构"] CodeF["code object: f<br/>(普通函数逻辑)"] CodeA["code object: A<br/>(类体内执行环境)"] end

subgraph Phase2["2. 外层调用准备"] LoadBC["LOAD_BUILD_CLASS<br/>(从 builtins 加载函数)"] MakeFunc["MAKE_FUNCTION A<br/>(将 code object A 封装为函数)"] end

subgraph Phase3["3. 运行并生成类"] CallBC["CALL_FUNCTION<br/>(执行 __build_class__)"] StoreA["STORE_NAME A<br/>(将返回的 type 保存为变量 A)"] end

CodeF --> CodeA CodeA --> MakeFunc LoadBC --> CallBC MakeFunc --> CallBC CallBC --> StoreA ```

从字节码结构来看,整个定义过程可以拆分为三个层次: 1. **最内层的成员方法 `f`**:位于最底部的 `code object f` 是最简单的部分。在类内部定义的函数,在底层实际上与在类外部直接定义的普通函数没有任何区别,它同样是一个独立的 `code object`。在类外部写一个接收参数并打印 `1` 的函数,编译出来的字节码与其完全一致。 2. **中间层的类代码块 `code object A`**:对于很多人来说,这一段字节码相对较难理解。码农高天解释道,它本质上“相当于 class A 冒号里面的内容,它单独拿出来变成了一小段程序”。在这段程序执行业务逻辑前,解释器先做了一些准备工作:设置了内置变量 `__module__ = __name__` 以及 `__qualname__ = 'A'`。接着执行第 2 行的赋值 `name = 'AA'`,并在接下来的字节码中创建了一个名为 `A.f` 的函数对象,将其保存在当前环境的局部变量 `f` 中。换句话说,整个类体可以看作是一个拥有独立局部变量命名空间的小函数,其运行过程就是为其中的局部变量赋值。 3. **最外层的类构建逻辑**:在顶层字节码中,首先出现的是 `LOAD_BUILD_CLASS`。查阅官方文档可知,这一步是将内置模块 `builtins` 中的 `__build_class__` 函数推入栈顶。接着将 `code object A` 封装成一个名为 `A` 的函数对象,然后调用 `CALL_FUNCTION` 执行刚才载入的 `__build_class__`,最后将执行返回的结果保存到名为 `A` 的变量中。码农高天在此提前剧透:“这个值可以提前剧透给大家,是一个 type。”

虽然整体脉络清晰,但一个关键疑问随之产生:类体函数在运行时仅仅是对其内部的局部变量进行赋值,这些局部变量究竟是如何脱离临时的局部作用域,最终成为 `A` 这个类的属性与方法的?

- 类体内定义的方法 `f` 本质上就是普通的 `code object`,与顶层定义的函数没有区别。 - 类体代码被打包成独立的 `code object A`,执行时会隐式设置 `__module__` 和 `__qualname__`。 - Python 解释器通过 `LOAD_BUILD_CLASS` 获取 `builtins.__build_class__`,并将类体函数和类名字符串作为参数传入执行。

---

[05:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=300s) CPython 源码探秘:`__build_class__` 与 `type` 对象的实例化

带着关于局部变量如何挂载的疑问,码农高天带领观众进入了喜闻乐见的 CPython 源码阅读环节,深入剖析 `builtins` 模块中的 `builtin___build_class__` 实现。

传入 `__build_class__` 的参数主要包括两个:类体生成的函数对象 `func` 和表示类名的字符串 `name`。在 C 源码中,函数先将这两个参数分别解包取出;如果存在继承关系,还会把基类列表存入 `mro` 或 `bases` 变量中。跳过复杂的元类处理后,代码在默认无基类的情况下,会将元类 `meta` 赋值为 `&PyType_Type`(即 Python 中的 `type`)。码农高天强调:“我们所有在 Python 里面直接通过 class 什么什么定义的 class,class 本身的 type 都叫 type。”在代码中直接打印 `type(A)`,验证了类对象本身的类型确实就是 `type`。

随后,源码进入关键的命名空间构建与代码执行阶段: - **创建命名空间字典 `ns`**:在基础场景下,变量 `ns` 被直接初始化为一个空的 Python 字典(`dict`),`ns` 即 `namespace` 的缩写。 - **执行类体字节码(`execute`)**:源码在 222 至 224 行执行了类体对应的 `code object`,并把字典 `ns` 作为保存局部变量(`locals`)的映射空间直接传入。码农高天形象地指出,这在 Python 层面相当于“新开了一个小环境”或者做了一次 `exec` 操作。在 `name = 'AA'` 和 `def f(): print(1)` 执行完毕后,产生的局部变量以键值对的形式被悉数写入 `ns`:包含了键 `'name'` 对应值 `'AA'`,以及键 `'f'` 对应新生成的函数对象 `f`。

```mermaid flowchart LR accTitle: CPython type 实例化与字典绑定 accDescr: 展示 namespace 字典如何通过 type_call 和 type_new 最终绑定到类对象的 tp_dict 上。

subgraph Execution["1. 局部环境执行"] NS["ns (空 dict)"] --> Exec["运行 code object A"] Exec --> FilledNS["ns 写入键值对:<br/>'name': 'AA', 'f': &lt;func&gt;"] end

subgraph TypeCall["2. 调用元类 type"] FilledNS --> VCall["vectorcall 调用 meta (type)"] VCall --> TCall["type_call (tp_call)"] end

subgraph TypeNew["3. 底层结构装配"] TCall --> TNew["type_new (tp_new)"] TNew --> CopyDict["copy ns -> dict"] CopyDict --> Bind["赋给 type->tp_dict<br/>(即 A.__dict__)"] end ```

接下来便进入了真正产生类或类型的核心步骤:源码构建了一个包含 `name`、`bases` 和 `ns` 的参数数组,发起 `vectorcall` 调用元类 `meta`(即 `type`)。因为 `type` 本身也是一个 Python 对象,当调用它时会触发其类型槽函数 `tp_call`(指向 C 源码中的 `type_call`)。在 `type_call` 中,依次触发了 `type->tp_new`(在底层对应 `type_new` 函数)以及 `type->tp_init`: - 在 `type_new` 函数中,参数被依次解析,若未指定基类则默认继承 `PyBaseObject_Type`(即 `object`)。 - 关键逻辑出现在 2454 行前后,源码将传入的原始命名空间字典拷贝一份存入 `dict`。 - 在 2643 行,这个 `dict` 被正式赋值给新建类型的 `tp_dict` 字段。码农高天揭秘道,这个 `tp_dict` 就是 Python 中的 `__dict__`;当后续在实例或类上访问 `a.f` 时,解释器正是通过该字典找到对应的函数对象。

- `builtin___build_class__` 接收类体函数和类名,若未显式指定 metaclass,默认使用 `type`。 - 类体代码执行时,将一个空的 Python 字典作为 `locals` 传入,所有变量定义和方法声明直接写入该字典。 - `type` 的调用会触发 C 语言底层的 `type_call`,进而调用 `type_new`(对应 Python 层的 `__new__`)。 - 类属性字典 `tp_dict` 直接由传入的 `ns` 拷贝并绑定,构成了后续属性查找的基础。

---

[10:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=600s) 运行验证与动态建类:`type` 的三个要素与历史包袱

为了验证上述机制,码农高天在代码中打印了 `A.__dict__`。输出结果清晰地证实了底层逻辑:其内部包含 `__module__: '__main__'`、`name: 'AA'` 以及指向函数 `<function A.f>` 的 `f`,与字节码分析的结果完全吻合。

码农高天对类定义的本质进行了通俗总结:“当我们定义一个新的 class 的时候,首先相当于运行了所有在这个 class 里面的代码,然后把产生的所有局部变量的名字和他们对应的值都保存到了这个 class 的 `__dict__` 里面,接着我们建立了一个 type,做很多我们今天没有讲的事情,然后把这个 type 赋值给了 class 名字的变量。”

由此引申出一个核心结论:类定义并不是什么神奇的特殊语法,其内部就是一段普通的可执行代码。除了通过 `class A:` 进行静态定义外,开发者完全可以在运行时动态创建一个类。动态构建类只需要三个核心要素: 1. **类的名字(name)**:字符串,如 `'A'`。 2. **父类元组(bases)**:包含所有基类的元组,若不继承其他自定义类则传入空元组 `()`。 3. **命名空间字典(dict)**:保存属性与方法的字典,例如 `{'name': 'AA', 'f': f}`。

利用内置的 `type` 函数,传入这三个要素:`type('A', (), {'name': 'AA', 'f': f})`,所得到的结果与使用 `class` 语法定义的类完全等价,二者在底层调用的甚至就是同一个 C 函数。

最后,码农高天解释了 Python 语法中一个常见的困惑:为什么有时 `type(x)` 查类型只传一个参数,有时却能传三个参数动态建类?“type 这个东西他在 take 一个 argument 的时候返回的是这个东西的 type,而在 take 三个 argument 的时候他返回的是一个新的 type,这件事其实是一个 Python 的历史包袱。”虽然动态建类在实际日常业务开发中并不算十分常见,但理解这一底层统一性,能够帮助开发者彻底剥除面向对象语法的神秘面纱。

- 静态定义 `class` 与动态调用 `type(name, bases, dict)` 底层逻辑完全等价。 - 动态建类三要素:类名字符串、父类元组、包含属性和方法的命名空间字典。 - 单参数 `type(obj)` 返回对象类型,三参数 `type(name, bases, dict)` 创建新类,这是 Python 设计保留的历史特性。

## Notable quotes > [10:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=600s) type 这个东西他在 take 一个 argument 的时候返回的是这个东西的 type,而在 take 三个 argument 的时候他返回的是一个新的 type,这件事其实是一个 Python 的历史包袱。 — 码农高天

> [05:00](https://www.youtube.com/watch?v=2MB-fkS3b70&t=300s) 我们所有在 Python 里面直接通过 class 什么什么定义的 class,class 本身的 type 都叫 type。 — 码农高天

## Mentioned Python, CPython, 码农高天, type, builtins, __build_class__, PyType_Type, PyBaseObject_Type

## Video description 这期视频聊一下class,主要是定义class背后的机制,让大家知道,class的定义,并不是什么特殊的神奇的事情,它也就是一段可运行的python代码而已。又有大家喜闻乐见的读cpython源码环节啦!

[Source](https://www.youtube.com/watch?v=2MB-fkS3b70)

中文简报

CPython将class定义转为普通函数 核心机制等同动态type调用 Python类声明本质上是在独立命名空间中执行代码体并收集局部变量,最终交由底层type构造器绑定为类属性,与运行时直接调用三参数type函数完全等价。 视频首先通过字节码拆解了一个极简 Python 类的定义过程,展示类体代码如何被编译为独立的 code object 并通过内置的 __build_class__ 进行调用。随后深入 CPython 源码,详细剖析了 builtin___build_class__ 如何将类体作为局部环境执行、把生成的局部变量写入命名空间字典,并最终通过元类 type 的底层实现(type_call 与 type_new)将字典挂载为类的 tp_dict。最后,作者演示了使用动态三参数 type(name, bases, dict) 手动构建类的方法,阐明静态 class 语法与底层动态建类完全等价的本质。 Python 中的 class 定义在底层并非特殊黑盒,而是将类体包装为一个独立的 code object 并在临时命名空间中执行的可运行代码。 (0:00) CPython 解释器通过 `LOAD_BUILD_CLASS` 调用 `builtins.__build_class__`,默认使用元类 `type` 来完成类对象的装配。 (5:00) 类体内产生的所有局部变量会被写入临时字典,并最终拷贝绑定到底层 C 结构的 `tp_dict`(即 Python 层的 `__dict__`)中供后续属性查找。 (5:00) 静态 `class` 语法与动态调用 `type(name, bases, dict)` 在底层完全等价,三参数 `type` 是 Python 设计中保留的历史特性。 (10:00)

Reading summary

码农高天指出,Python 中使用 class 语法定义类并非黑盒魔法,其本质是解释器将类体作为独立代码块执行,并将产生的局部变量收集到命名空间字典中,最终调用元类 type 完成类对象的构建。 字节码层面的三层封装与执行准备 (0:00) 在字节码层面,一个四行代码的极简类定义被拆分为三个执行层次。最内层的方法定义与类外部普通函数完全一致,编译为独立的 code object;中间层的类体同样被打包为一个独立的 code object,执行时隐式设置 `__module__ = __name__` 和 `__qualname__ = 'A'`,并像普通小函数一样在当前作用域内对局部变量和函数进行赋值。最外层逻辑则通过 `LOAD_BUILD_CLASS` 从内置模块载入 `__build_class__` 函数,将封装好的类体函数与类名传入,等待执行返回结果。 CPython 源码中命名空间的收集与绑定 (5:00) 在 CPython 源码的 `builtin___build_class__` 中,解释器默认将元类 `meta` 指向 `&PyType_Type`。源码会初始化一个空的命名空间字典 `ns` 并作为局部变量映射传入类体代码的执行环境;类体内部的变量赋值和方法定义完成后,均以键值对的形式直接存入 `ns`。 随后,解释器通过 `vectorcall` 携带类名、基类列表和 `ns` 调用元类 `type`,触发 C 层的 `type_call` 与 `type_new`。在 `type_new` 中,若未显式指定基类则默认继承 `object`,传入的 `ns` 字典被拷贝并正式赋值给新建类型的 `tp_dict` 字段(即 Python 层的 `__dict__`),为后续的类属性与方法查找提供支撑。 动态建类的三要素与历史接口设计 (10:00) 类定义的本质是先执行类内代码以收集局部变量,再调用元类实例化出 type 对象。因此,开发者无需使用 `class` 关键字,也可以直接通过 `type(name, bases, dict)` 在运行时动态创建类:只需提供字符串类型的类名、包含继承关系的父类元组,以及包含属性与方法的命名空间字典。这种动态方式与静态 `class` 语法底层调用的 C 函数完全一致。至于 `type` 既能以单参数查询对象类型,又能以三参数动态创建新类,则是 Python 语言演化中保留下来的历史特性。 理解类定义的底层统一性,能够帮助开发者脱离面向对象语法的表象,看清类体在本质上就是一段收集局部变量并传给元类实例化的常规执行代码。

Source quotes

[10:00] 码农高天: type 这个东西他在 take 一个 argument 的时候返回的是这个东西的 type,而在 take 三个 argument 的时候他返回的是一个新的 type,这件事其实是一个 Python 的历史包袱。 [5:00] 码农高天: 我们所有在 Python 里面直接通过 class 什么什么定义的 class,class 本身的 type 都叫 type。

Original transcript

[0:00] 这期视频我们来聊一下class在我们平时写python的过程中呢,肯定是会经常写class的但是你有没有想过在你定义一个class的时候Python的内部实际上发生了什么事情呢我们看左边我定义了一个非常简单的class它的名字是A它里面有一个member variable叫name我还定义了个method叫f那我们老规矩我们看一下这段代码的字节码是什么那可以看到短短的四行代码它产生的字节码还是比较复杂的我们来一段一段的分析一下首先是最后这个部分 这个部分是最简单的它是我们第三行第四行定义的这个F函数我们在这可以看到在class里面定义的F函数它实际上和在class外面定义的函数没有任何区别它也是一个code object如果你感兴趣的话你在外面试一下定义一个F函数take一个argumentprint1他的字节目也是这样的那第二个部分就是这个code object a他对于很多人来说可能相对来说就没有那么容易理解了它本质上相当于什么呢你可以把它理解成classA冒号里面的内容它单独拿出来变成了一小段程序当然在这个程序之前他还做了一些零七八岁的事那我们可以先看这个0246他相当于是做了一个下位线下位线module等于下位线下位线内然后又做了一个下锅下锅下锅 name 等于 A翻译成代码就是这么两句话然后8跟10实际上对应的就是我们第二行 name=AA接下来12到22是我们做了一个函数这个函数的名字叫A.F然后我们把这个函数保存在了F这个变量里那从你的角度看呢 实际上你可以把这一个部分理解成一个小函数这个函数有一个局部变量的空间这个函数运行的时候实际上就是把函数里面的很多局部变量附上了值这些局部变量包括内置的module call name也包括你在这写的name和f好那接下来我们看在最外层运行的代码这里有个load build class那我们在python的官方文档可以找到它实际上就是push了这个builtins里面的buildclass函数我们等会儿会进到这个函数里面去讲那我们再来看下面的246是我们做了一个名字叫A的函数使用的是刚才的CodeObject A也就是对应原代码的234行我们刚才说的做的那个小函数接下来我们做了一个call function这个function就是我们刚才提到的build class然后我们把这个方式的返回值保存到了A这个变量里这么一通操作下来实际上我们就是建立了一个新的名字叫做A的变量这个变量里面保存的值是Build class这个Build in函数所返回的一个值这个值可以提前剧透给大家 是一个type但是说到这儿呢 其实我们还有很多疑问没有解开比如说刚才的那个函数运行的时候它只是在函数内部的局部变量里面复制了那这些局部变量是怎么成为A这个class的一部分的呢那我们带着这个问题去看一看这个build class 参数好,我们看这就是我们这个Build-in里面的Build-class 参数大家回忆一下我们刚才传进这个build class函数里面有两个argument第一个是函数A就是class里面缩近的那一块做的函数第二个Argument实际上是一个字符串A是这个class的名字我们看在114行他就把这个function给拿出来复制到了funk上而120行把这个名字拿出来复制到了name上那这当然是相对比较简单的情况如果还有继承关系的话它会把你继承的class拿出来存到这个outread basis里面我们跳过一些跟meta class有关的代码 直接看到第165行我们这次介绍是一个比较简单的情况有关meta class的事我们以后有机会再去聊那这里呢实际上就是判断了一下你有没有传进来一个meta class那在没有base情况下meta就直接被复制成了type type那它的原代码就在这个typeobject里面我们所有在python里面直接通过class什么什么定义的classclass本身的type都叫type这句话听着有点绕啊我们回到刚才的代表 [5:00] 我们定义了class A之后 我们打印一下A的type注意我们打印的是这个class的type而不是这个class产生的object的type可以看到这个class type就是type那接下来呢在这里我们注意一下在我们这个最简单的case下ns这个变量是被直接复制成了一个空的dictionary这个ns就是namespace的意思这个变量NS会被拿来保存A里面的局部变量接着往下看第222到第224行这里相当于做了一个executeExecute是什么呢?就是刚才A的那个code object然后注意第222行的最后ns也就是namespace这个dictionary被作为保存local variable的变量给传进去了那如果站到拍葬的角度说实际上它就是新开了一个小环境或者如果你知道什么是exec的话它实际上做了一个exec然后他运行了一下name等于AA又运行了一下deff 冒号print1这个时候它的局部变量有一个内还有一个F而这些局部变量被以key value pair的形式保存到了ns这个dictionary里也就是在做完这个Execution之后NS这个Dictionary里边有一个key是nameValue是AAA这个Stream还有一个key是fvalue就是f现在这个function好 那我们接着往下看重头戏真正的产生class或者type的代码是在231、232行首先我建立了一个array里面分别给了name basis跟ns在我们这个case下basis里面没有东西然后我做了一个vector call这个vector call 你就可以把它理解为调用了meta函数传进去了m args而这个meta我们之前说了是一个type那type本身呢 当然也是一个python object当我们尝试去调用这个python object的时候呢他就会找这个tp call那对于type来说它的tpcall是typecall这个函数也就是他会运行这个type call我们看这里是这个type call函数我们往下找找到有趣的地方从第1021行开始就是我们正式建立这个class的代码了首先他做了一个typeTPnew这个东西以后我们会知道实际上就是下右线下右线NU当然在1033行他还做了一个TPinit他就是下锋线下锋线init当然了 严格上来说应该是Python的那个Magic Method对应到C语言里面是这个因为最后运行的是C语言那我们就去看一下这个type的tpnew是什么我们可以看到这里type的tpnew就是typenew这个函数在type new这个函数里他首先把这个arguments给读出来了我们说过basis是空的对吧他把name读到了name里然后把dict读到了origdict里这是argument的准备工作然后这块有一个旁之细节如果你没有basis也就是你不继承任何东西的话它会认为你继承了这个pyBaseObject也就是默认你继承了这个Object Type当然刚才那个知识对今天来说不是必要的那必要的呢在这里在2454行他把这个 "Origed" copy 了一份复职到了Dict里这个dict大家可能已经猜到了就是未来这个class点下号线下号线dict里面保存的内容好 我们跳过一些暂时无关的代码 未来我们有机会讲在2643行呢他就把这个dict给复制给了typeTPData而tpdict就是它的下午线下午线dict当你在做a.f的时候他就会在这个dict里面寻找名字是f对应的那个东西也就是现在A.下方向Dick已经被复制成了刚才我们的局部变量的Dictionary然后下面这块代码呢就是如果这个dict里面没有module的话他在想办法把这个module复制上当然其实我们前面说过局部变量里除了显示写的name跟f之外他实际上也写了这个module跟quanning当然在这段代码之后呢后面还有很多的代码我给大家看一下这些代码很多是跟Magic Method有关的比如说如果你重载了New会怎么样这些东西到未来 [10:00] 他一点一点讲我们先把最简单的情况先搞明白那到最后当然就是把这个type object给返回回去如果回到python代码 就是在call function之后 得到的type object被保存到了A这个变量里我们可以打印一下A. 下半小千dict就可以看到它的module是main对吧然后这里有一个NAME叫做AA然后F对应的是一个函数这个函数的名字就叫a.f这也是我们刚才在bycode里面看到的简单总结一下当我们定义一个新的class的时候首先相当于运行了所有在这个class里面的代码然后把产生的所有局部变量的名字和他们对应的值都保存到了这个class的点下方线下方线Dict里面接着我们建立了一个泰国做了很多我们今天没有讲的事情然后把这个type复制给了class的名字的变量那实际上除了我们像平时这么正常的静态的定义一个类之外 我们是可以动态的建立一个类的而这个过程就是我们刚才说的那个完整的过程建立一个类需要三个东西第一这个类的名字第二它的负类第三就是这个Dictionary而有了这三个东西我们就可以用type这个函数动态的去建立一个类那我们看我们左边现在这代码跟我们刚才写的那个class是等价的他们甚至调用的都是同一个函数那这里呢你可能会发现type这个东西他在take一个argument的时候返回的是这个东西的type而在take三个argument的时候他返回的是一个新的type这件事其实是一个拍档的历史包袱在这里给大家说明一下那包括这个用法本身其实也不是那么常见大家就当做拓展知识 学习一下好 那这期视频就到这里 如果喜欢的话记得点一下关注 别忘了意见三连哦字幕志愿者 杨茜茜