CPython虚拟机机制拆解 解释器通过Switch无限循环派发栈指令

【python】字节码和虚拟机?python代码竟然是这么执行的! — 码农高天

码农高天

Python程序运行并非不可探知的黑盒,其底层依托Frame上下文维护的栈架构,在C语言死循环中经由Switch分支逐条派发并计算字节码。

AI-assisted summary of 【python】字节码和虚拟机?python代码竟然是这么执行的! — original source. Check the original for full context.

Source published:

Key ideas

  • Python 代码在执行前会被编译为 code object,并在运行时由解释器分配的执行帧(frame)中执行,每个函数调用均会生成独立的 frame 来维护局部变量与执行上下文。 (0:00)
  • Python 虚拟机采用基于栈(stack-based)的架构,绝大多数字节码均通过压栈、出栈并结合 code object 元数据进行计算与数据传递。 (1:40)
  • CPython 的核心字节码执行引擎集中在 Python/ceval.c 的 _PyEval_EvalFrameDefault 函数中,其本质是一个包含巨大 switch 分支的无限循环,且支持被第三方 JIT 工具(如 Numba)重载接管。 (3:45)
  • 字节码的执行逻辑与底层硬件汇编语言在原理上完全相通,区别仅在于汇编由物理 CPU 执行,而 Python 字节码由 C 语言实现的软件虚拟机执行。 (5:00)

Detailed notes

## Background 该视频由“码农高天”于2023年10月10日发布在YouTube平台。视频旨在对Python的底层机制进行入门级介绍,重点拆解经常被提及的字节码与虚拟机的基础概念和运行链路。

## TL;DR 视频从Python代码编译生成的code object入手,阐述了执行帧(frame)作为运行环境如何承载局部变量、全局变量和指令序列。接着结合dis模块,以简单的加法函数为例,解释了Python基于栈(stack-based)的字节码工作逻辑。最后深入CPython源码的ceval.c文件,剖析了_PyEval_EvalFrameDefault中庞大的死循环与switch派发机制,揭示了LOAD_FAST、BINARY_ADD等指令在C语言层面的真实操作与退栈流程。

## Atmosphere & style 讲解风格清晰沉稳、循循善诱,既有条分缕析的架构梳理,又有直接对照源码细节的扎实论证。博主语气亲切务实,在剖析庞大的底层C代码时不断宽慰并鼓励读者消除畏难情绪,整体呈现出一种去神秘化、通俗易懂且极具启发性的技术拆解氛围。

## Bulletin - [00:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=0s) Python 代码在执行前会被编译为 code object,并在运行时由解释器分配的执行帧(frame)中执行,每个函数调用均会生成独立的 frame 来维护局部变量与执行上下文。 - [01:40](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=100s) Python 虚拟机采用基于栈(stack-based)的架构,绝大多数字节码均通过压栈、出栈并结合 code object 元数据进行计算与数据传递。 - [03:45](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=225s) CPython 的核心字节码执行引擎集中在 Python/ceval.c 的 _PyEval_EvalFrameDefault 函数中,其本质是一个包含巨大 switch 分支的无限循环,且支持被第三方 JIT 工具(如 Numba)重载接管。 - [05:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=300s) 字节码的执行逻辑与底层硬件汇编语言在原理上完全相通,区别仅在于汇编由物理 CPU 执行,而 Python 字节码由 C 语言实现的软件虚拟机执行。

## Key takeaways - [00:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=0s) 学习 Python 虚拟机与字节码底层机制有助于在阅读 CPython 源码时建立全局认知,理解程序各模块是如何串联运行的。 - [00:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=0s) 所有 Python 代码在运行前都会先被编译成 code object 数据对象。 - [00:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=0s) 运行时创建的 frame 提供了局部变量、全局变量和指令序列的环境上下文,并通过记录上一个执行的指令来实现条件分支与跳转。 - [00:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=0s) 除了最外层的根 frame 外,每一次函数调用都会在虚拟机中新建一个对应的子 frame。 - [01:40](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=100s) 利用 Python 内置的 dis 模块可以将纯二进制字节码反编译为人类可读的助记符指令(如 LOAD_FAST、BINARY_ADD)。 - [01:40](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=100s) Python 虚拟机基于栈设计,典型加法函数仅需 LOAD_FAST 压栈、BINARY_ADD 计算后回栈、RETURN_VALUE 弹栈返回四步指令。 - [01:40](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=100s) 分析不确定的 Python 底层逻辑时,查阅官方 dis 模块文档或直接反编译查看字节码是最有效的手段之一。 - [03:45](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=225s) CPython 解释字节码的核心实现位于 Python/ceval.c 源码文件中。 - [03:45](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=225s) _PyEval_EvalFrameDefault 是 CPython 中最庞大的核心执行函数,也是默认评估 frame 的入口。 - [03:45](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=225s) CPython 允许重载 _PyEval_EvalFrameDefault,Numba 等 JIT 编译工具正是借此机制介入并替换底层执行流程以实现加速。 - [05:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=300s) _PyEval_EvalFrameDefault 内部通过 main_loop 无限循环和 switch(opcode) 进行操作码派发与处理。 - [05:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=300s) LOAD_FAST 在 C 源码层面通过 GETLOCAL 读取局部变量、Py_INCREF 增加引用计数并 PUSH 压入栈顶,随后 FAST_DISPATCH 派发下一条指令。 - [05:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=300s) RETURN_VALUE 分支会将当前 frame 标记为 FRAME_RETURNED,并通过 goto exiting 打破主死循环退出当前帧。 - [05:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=300s) CPython 字节码解释引擎的逻辑模块清晰、贴近直觉,研读其底层源码并不神秘也不深奥。 - [05:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=300s) Python 虚拟机的栈操作与状态机循环在设计原理上等同于汇编语言,只是承载计算的实体从物理 CPU 替换为了软件解释器。

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=0s) Python 虚拟机与 Frame 的执行环境

在之前的诸多视频中,“bytecode”(字节码)以及“Python 虚拟机”这两个词经常被提及,但一直没有专门讲解虚拟机究竟是如何工作的、其底层机制与工作原理是什么。码农高天强调,做这期视频并不是要让人一下子彻底搞懂 Python 虚拟机的全部细节,而是给大家一个基础概念,建立起全局认知:“这期视频的目的倒不是让你完全理解python的虚拟机,只是为了让你以后再看我的视频的时候,可以知道我在看这块的CPython代码是因为虚拟机是这么连起来的,让你对你的python程序是怎么运行的哎有一个大致的了解。”

所有的 Python 代码在执行前都会被编译成 `code object`。在正式运行代码之前,Python 解释器会建立一个执行帧(`frame`)。借助 Python 标准库的 `inspect` 模块,可以看到许多与 `frame` 相关的信息: - `frame` 为程序提供了一个运行环境,内部包含了代码的局部变量(local variables)、全局变量(global variables)以及该代码对应的字节码。 - `frame` 中还有一个非常关键的信息,即上一个运行的 instruction(上一个执行的 bytecode)。通过在运行时改变它,程序便能完成条件分支或跳转等控制流操作。 - 程序刚启动时会建立初始的根 `frame`;此后,代码中每一次发生函数调用(function call),解释器都会为其新建一个对应的 `frame`。因此,除了最外层的主程序外,每一个 `frame` 都对应着一次函数调用,所有的 bytecode 都是在一个具体的 `frame` 内部被顺序执行的。

```mermaid flowchart TD accTitle: Python Code Execution Setup accDescr: Python code compiles to a code object, which runs within execution frames that hold variables and bytecode instructions. subgraph Compilation["编译阶段"] A["Python 源代码"] --> B["Code Object (代码对象)"] end subgraph Execution["运行时环境 (Frame)"] B --> C["建立 Frame"] C --> D["局部变量与全局变量"] C --> E["Bytecode 指令序列"] C --> F["上一个运行的 Instruction (控制分支)"] end C -->|"函数调用 (Function Call)"| G["新建子 Frame"] ```

[01:40](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=100s) 基于栈(Stack-Based)的字节码与 dis 模块解析

一旦建立了 `frame`,Python 解释器就会开始逐条执行字节码。底层实际存储与运行的字节码是纯二进制数据,但可以通过 Python 自带的 `dis` 模块将其反编译并打印成人类可读的形式,例如展示出 `LOAD_CONST`、`LOAD_NAME`、`CALL_FUNCTION` 等指令。

Python 虚拟机在架构上是典型的“基于栈”(stack-based)设计。简而言之,绝大多数字节码指令要么是在对数据做运算,要么就是在对栈(stack)进行压栈、出栈等操作。以一个仅包含简单加法运算的 `add(a, b)` 函数为例,它被编译后仅有 4 条字节码指令: 1. `LOAD_FAST 0`:将位置为 `0` 的局部变量(即参数 `a` 的值)压入栈中。这里位置参数 `0` 对应参数 `a`,来源于编译期生成的 `code object`。 2. `LOAD_FAST 1`:将位置为 `1` 的局部变量(即参数 `b` 的值)取出来压入栈中。此时栈中包含 `a` 和 `b` 两个数据。 3. `BINARY_ADD`:从栈顶依次弹出这两个数据,计算它们的加和(`a + b`),然后把相加的结果重新压回栈中。此时栈内仅剩一个元素,即计算结果。 4. `RETURN_VALUE`:将当前栈顶的数据弹出并返回给函数的调用者,最终完成了整个函数的返回。

若想了解各个字节码的具体功能,查阅 Python 官方文档中关于 `dis` 模块的说明是最权威的途径。例如: - 文档中对 `LOAD_FAST` 的解释是:将 `co_varnames` 中索引为 `var_num` 的变量压入栈顶。 - 对 `BINARY_ADD` 的描述为实现 `TOS = TOS1 + TOS`。其中 `TOS` 代表 Top of Stack(栈顶),`TOS1` 代表栈顶下方紧邻的元素。 - 对 `RETURN_VALUE` 的定义是将栈顶的数据直接返回给调用该函数的调用者。

码农高天总结道:“所以当你不确定你的Python代码的底层是如何实现的时候呢,把它转成字节码然后再去读它的字节码是一个很有效的方式,这个方式呢我们之前也实践过非常多次了。”

[03:45](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=225s) 深入 CPython 源码:ceval.c 与 _PyEval_EvalFrameDefault

如果觉得官方文档关于字节码的说明仍然不够详尽,想要进一步了解其在 C 语言层面的真实实现,就必须深入 CPython 的源码。在 CPython 源码树中,负责对 `frame` 进行求值(evaluation)的核心逻辑存放在 `Python/ceval.c` 文件中。

该文件中最核心的函数是 `_PyEval_EvalFrameDefault`: - 该函数承担了标准 CPython 执行字节码的任务。函数体极为庞大,“它很有可能是CPython里面最长的函数”。 - CPython 允许用户重载该函数。一些依靠 JIT(即时编译)对 Python 代码进行加速的第三方库(例如 Numba)正是利用了这一机制,通过重载 `_PyEval_EvalFrameDefault` 来介入和接管代码的执行。但在不使用 JIT 扩展的绝大多数日常场景下,CPython 都是在默认运行这个巨大的函数。

[05:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=300s) 核心循环机制:Infinite Loop、Switch 派发与退栈流程

在 `_PyEval_EvalFrameDefault` 内部,执行字节码的核心结构是一个主死循环(无限循环)。在源码约 1392 行处,可以看到标有 `main_loop` 标签的无限循环逻辑。在跳过分析和 tracing 代码之后,循环内部实际上是一个巨大的 `switch (opcode)` 语句,它根据当前读取到的操作码(opcode)跳转到对应的分支处理逻辑,构成了运行字节码的中枢。

在这个大 `switch` 中,之前提到的几条加法指令在 C 语言层面的执行逻辑清晰明了: - **`LOAD_FAST`(约 1500 行)**: 1. 通过 `GETLOCAL(oparg)` 根据偏移量变量(即操作数 `0`, `1`, `2` 等)读取对应的局部变量并存入临时变量。 2. 进行错误检查。 3. 执行 `Py_INCREF` 增加对象的引用计数(reference count)。 4. 调用 `PUSH` 宏将该对象指针压入虚拟机栈顶。 5. 执行 `FAST_DISPATCH()`,其语义等价于 `continue`,立即跳入下一次循环去拉取并处理下一条字节码。 - **`BINARY_ADD`**: 1. 从栈顶 `POP` 出一个操作数,再获取新的栈顶元素。 2. 对两者执行加法运算(底层还包含针对字符串拼接等场景的特殊优化)。 3. 通过 `SET_TOP` 将加法得出的结果重置为新的栈顶元素,随后继续派发下一条指令。 - **`RETURN_VALUE`**: 1. 该分支并不以 `FAST_DISPATCH()` 继续循环,而是对当前帧的状态进行标记,将内部状态修改为 `FRAME_RETURNED`,代表当前 `frame` 的生命周期已经结束。 2. 代码末尾通过 `goto exiting` 跳转到退出逻辑。这与此前在讲解 `async` 异步机制时提到的退出路径完全一致,旨在彻底打破外层的无限循环,并将栈顶的最终结果返回给上层调用者。

码农高天宽慰大家在面对底层源码时不要有畏难情绪:“大家想一下这个load fast是不是其实很简单,它没有什么难的啊,就如果你认真的去读的话,你会发现它并不是一个很神秘的东西,它就是一些很简单的事情……所以大家可以看到呢,研究python代码在底层究竟是如何工作的,其实不是一个很困难很复杂的事情,每个人都是有能力来完成这件事的。”

从全局来看,Python 代码的运行闭环可以概括为:每次调用函数或启动程序时生成 `frame`,在 `frame` 构成的上下文中维护一个栈,虚拟机通过无限循环和分支选择逐条执行字节码;字节码负责操作栈或与 `code object` 存储的元数据交互并进行计算,计算结束返回结果后销毁或退出该帧。对于熟悉汇编语言的工程师来说,这一套机制在原理上与底层汇编完全一致,唯一的差别仅在于汇编语言是直接由物理硬件 CPU 执行,而 Python 字节码则是运行在用 C 语言构建的软件虚拟机之上。

## Notable quotes > [01:40](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=100s) 当你不确定你的Python代码的底层是如何实现的时候呢,把它转成字节码然后再去读它的字节码是一个很有效的方式 — 码农高天

> [05:00](https://www.youtube.com/watch?v=FIot_8h5JzQ&t=300s) 研究python代码在底层究竟是如何工作的,其实不是一个很困难很复杂的事情,每个人都是有能力来完成这件事的。 — 码农高天

## Mentioned 码农高天, Python, CPython, Numba, inspect, dis, ceval.c, _PyEval_EvalFrameDefault

## Video description 今天聊一下python的底层机制,对字节码和虚拟机的部分进行一个入门级别的介绍,给大家一个基础概念,因为确实之前的视频中出现过太多次了。

[Source](https://www.youtube.com/watch?v=FIot_8h5JzQ)

中文简报

CPython虚拟机机制拆解:解释器通过Switch无限循环派发栈指令 Python程序运行并非不可探知的黑盒,其底层依托Frame上下文维护的栈架构,在C语言死循环中经由Switch分支逐条派发并计算字节码。 视频从Python代码编译生成的code object入手,阐述了执行帧(frame)作为运行环境如何承载局部变量、全局变量和指令序列。接着结合dis模块,以简单的加法函数为例,解释了Python基于栈(stack-based)的字节码工作逻辑。最后深入CPython源码的ceval.c文件,剖析了_PyEval_EvalFrameDefault中庞大的死循环与switch派发机制,揭示了LOAD_FAST、BINARY_ADD等指令在C语言层面的真实操作与退栈流程。 Python代码在执行前会被编译为code object,并在运行时由解释器分配的执行帧(frame)中执行,每个函数调用均会生成独立的frame来维护局部变量与执行上下文。 (0:00) Python虚拟机采用基于栈(stack-based)的架构,绝大多数字节码均通过压栈、出栈并结合code object元数据进行计算与数据传递。 (1:40) CPython的核心字节码执行引擎集中在Python/ceval.c的_PyEval_EvalFrameDefault函数中,其本质是一个包含巨大switch分支的无限循环,且支持被第三方JIT工具(如Numba)重载接管。 (3:45) 字节码的执行逻辑与底层硬件汇编语言在原理上完全相通,区别仅在于汇编由物理CPU执行,而Python字节码由C语言实现的软件虚拟机执行。 (5:00)

Reading summary

码农高天在视频中解析了 Python 代码的底层运行机制:源代码首先编译为 code object,并在执行时通过栈式虚拟机与执行帧(frame)逐条调度字节码完成运算。 执行环境:Code Object 与 Frame 的建立 (0:00) Python 代码在正式运行前会被编译成包含元数据与指令序列的 code object。在执行阶段,解释器为程序创建执行帧(frame),作为字节码运行的具体上下文。除了启动时的根 frame,每次发生函数调用都会新建对应的子 frame。 Frame 内部维护着局部变量、全局变量以及当前指令序列,并通过记录上一个执行的 bytecode 来实现分支与跳转等控制流控制。所有字节码指令都在对应的 frame 内按序推进。 基于栈的字节码架构与 dis 模块解析 (1:40) Python 虚拟机采用基于栈(stack-based)的架构设计,字节码的本质是纯二进制数据,其绝大多数指令均围绕栈的压栈(push)、出栈(pop)及运算展开。开发者可以通过标准库的 inspect 与 dis 模块对字节码进行人类可读的反编译分析。 以简单的 add(a, b) 函数为例,其底层仅由四条指令构成:LOAD_FAST 0 与 LOAD_FAST 1 依序从局部变量中取出参数 a 和 b 压入栈中;BINARY_ADD 弹出栈顶两元素并计算加和,再将结果压回栈顶(TOS);最后 RETURN_VALUE 将栈顶结果弹出并返回给上层调用者。 CPython 核心循环:ceval.c 与主循环派发机制 (3:45) 在 CPython 源码中,字节码求值的核心逻辑位于 Python/ceval.c 文件的 _PyEval_EvalFrameDefault 函数内。该函数承担了执行字节码的默认任务,第三方 JIT 框架(如 Numba)亦可通过重载此函数来接管执行逻辑。 该函数内部是一个带有 main_loop 标签的无限循环,中枢为一个庞大的 switch (opcode) 派发结构。以指令实现为例:LOAD_FAST 通过 GETLOCAL 获取变量、增加引用计数(Py_INCREF)并调用 PUSH 压栈后,执行 FAST_DISPATCH() 继续下一次循环;BINARY_ADD 执行出栈相加并将结果设为新栈顶;而 RETURN_VALUE 则将状态标记为 FRAME_RETURNED,通过 goto exiting 打破外层死循环并退出当前帧。 Python 代码的底层运作闭环可概括为:每次调用新建 frame 并在其上维护栈结构,C 语言实现的虚拟机通过主死循环与 switch 语句逐条解释执行字节码。这套机制在逻辑上与硬件 CPU 执行汇编语言基本一致,区别在于 Python 将执行环境抽象到了软件虚拟机层面。

Source quotes

[1:40] 码农高天: 当你不确定你的Python代码的底层是如何实现的时候呢,把它转成字节码然后再去读它的字节码是一个很有效的方式 [5:00] 码农高天: 研究python代码在底层究竟是如何工作的,其实不是一个很困难很复杂的事情,每个人都是有能力来完成这件事的。

Original transcript

[0:00] 在我们前几期视频里呢 我们经常会提到bycode这个词 也就是字节满然后我们也会说到Python的虚拟机这块会做什么事情但是我们从来没有专门讲过Python的虚拟机究竟是如何工作了它背后的工作机制是什么工作原理是什么今天我就简单的跟大家介绍一下python的虚拟机是怎么工作的这期视频的目的倒不是让你完全理解python的虚拟机只是为了让你以后再看我的视频的时候可以知道我在看这块的Cpython代码是因为虚拟机是这么连起来的让你对你的python程序是怎么运行的哎有一个大致的了解那在我们之前一期视频里提到过这个所有的python代码都会被compile成一个code object对不对那python的解释器呢在运行python代表之前会建立一个frame大家可以看这是inspect这个库里面给出的跟frame有关的一些信息frame不是我们今天的重点啊以后有机会我们介绍它你只需要知道frame提供了一个程序运行的环境这里面包含它的局部变量包含它的全局变量包含这段代码对应的bytecode然后还有很重要的一个是这个上一个运行的instruction 也就是上一个运行的bytecode那通过改变它呢 我们的代码就可以完成一些分支的操作除了最开始运行python代码建立的frame之外在之后每一次调用函数也会建立一个frame那你可以理解成我们的每一个Betcode或者说你所有的代码都是在一个frame里面执行的然后每一个frame除了最外面那个之外都是一个function call 是一个函数的调用每次建立了frame之后呢Python解释器就要开始一条一条的执行字解码了比如说我们左边这个简单的程序一个加法那么它翻译出来字节码就是右边这样的当然了实际上保存跟运行的字节码是二进址的数据在这里我们用dits这个酷把它给打印出来 打印成人类能看懂的形式那我们看这些字节码的类型 对吧load const load name call function这些都是直接码那Python的字节码呢是stack-based就是一个以战为核心的这么一个结构用比较简单的话说就是所有的字节码要不然就是在进行计算要不然就是在对战进行一个操作我们可以来看一下这个add函数编译成了字节码它只有四条很容易懂第一条叫Load Fast 0LoadFast就是把Local变量给压到站里如果你之前看过我有关codeobject的那期视频呢你就知道这里这个位置0为什么对应的是一个a接下来呢 他又运行了一个loadfast 这回是loadfast1那e对应的位置是b这个variable里保存的值然后把这个值再压占那我的站立现在就是A和BBinaryAd这个操作会从站顶拿出来两个数据,把它们加起来然后再给押回站立那么我现在站里就只有一个数据了就是A加B然后return value会把站最顶长的数据返回回去也就是返回了一个a+b那具体的每个字节码的功能是什么大家可以看官方文档关于this这个module里面的内容比如说我们可以找到loadfast它这里就写了它是把covar names里面的the var number 数据给压到站里去这个code object 知识是不是用上了那我们再看这个BinaryAdd 它说它实现了TOS等于TOS1加TOS那tos的意思呢就是top of stack 就是站顶的意思就他要实现站顶的数据等于站顶的数据再加上站顶下面一个数据同样的这边return value他就说我要把站顶的数据返回给这个function的调用者所以当你不确定你的Python代码的底层是如何实现的时候呢把它转成字节码然后再去读它的字节码是一个很有效的方式这个方式呢 我们之前也实践过非常多次了当然有的时候你觉得对吧我看官方文档的字节网说明我觉得还是不够详细我还想再深入的研究一下它在C语言的层面是怎么实现的那我们就只能去看cpy的原码了在cpython的原代码里面frame这个部分的evaluation的核心实现是在ceval.c文件里具体的是在这个py_eval eval frame default里面当然了 Python运行这个函数的前提是你正常的在使用cpython 实际上用户是可以自己重载这个函数的 这是cpython给的一个机制啊 比如说有些人可能知道number这样的靠jit来对python进行优化的一些酷啊 就是要重载这个函数但是正常情况下你运行Python都是运行的这个函数这个函数的函数体非常非常长啊它很有可能是cpy 里面最长的函数 [5:00] 那么不停的运行bytecode直到结束显然是一个循环该做的事情是不是那么在1392行这个infer loop就是这个东西的核心循环它上面有个label叫做main loop对不对我们跳过一些tracing的代码不看的话呢比较容易理解比较重要的代码就在这个地方它是一个非常非常大的switch就是根据这个upcode来做不同的操作这里就是运行bytecode核心我们可以看到在屏幕的中央1500行就是我们刚才聊的load fast对不对LoadFast就是让一个TemporaryValue等于GetLocalOpArcOpArc就是0 1 2 2 这些表示位置的一个变量拿到它之后做了一个错误检查如果没有问题的话首先要increment reference 增加一下reference count这个我们以后也是有机会再讲然后就是pushpush的话就是把它压到站顶然后fast dispatch我们讲过就是continue对不对还记不记得刚才外面是一个无线循环就是下一个bytecode了那大家想一下这个load fast是不是其实很简单它没有什么难的啊就如果你认真的去读的话你会发现它并不是一个很神秘的东西它就是一些很简单的事情那我们看BinaryAd 实际上在之前出境过 还记得1+1的视频吗 出境过它也没有很复杂 对不对 当你理解了这个虚拟机是一个stack based之后它实际上就是pop一下然后拿到top的东西 把它俩加起来 然后再set top就这么简单的一个事情只是它中间有一些关于String的优化那我们再看这个return value就稍微有点不一样了对不对他修改了一些跟这个frame有关的事情比如说他把这个state变成了frame return就是说这个frame已经结束了同时呢它的最后一行代码也不再是dispatch 而是go to exit如果你有一直在follow我的视频的话你会发现这个go to exit很眼熟对不对我们在讲async那期视频也提到了go through exiting 就是要退出这个无线循环然后准备返回值了所以大家可以看到呢研究python代码在底层究竟是如何工作的其实不是一个很困难很复杂的事情每个人都是有能力来完成这件事的总结一下就是当你每一次调用函数或者刚开始运行python的时候会建立一个新的frame然后在这个frame的环境下你会一条一条的运行bytecode每一条Bitcode在C语言里有相应的代码去执行它每一个frame Python会维护一个stack 一个站然后bytecode跟这个站进行交互当然也会和这个code object里面保存的那些乱七八东西进行交互接着就是进行计算拿到结果返回继续对吧就是这些东西如果你对汇编比较熟悉的话你会发现这些东西跟汇编就是完全是一套玩意儿只不过汇编是可以直接运行在硬件上的而python的bytecode是要运行在西语言上的就这么简单好 那这期视频就到这里 希望可以弥补一下我不同视频之间的鸿沟啊 如果喜欢的话记得点一下关注 别忘了一键三连哦