Python 是如何执行我的代码的?

理解 Python 如何执行你的代码,可以帮助我们解释很多“为什么”——为什么会有 .pyc 文件?为什么 Python 相对较慢?多线程为什么不能利用多核?

我们可以用一个“厨师做菜”的比喻来理解整个过程,然后再深入技术细节。


一、比喻:厨师(Python)根据菜谱(你的代码)做菜

想象一下,你是一位顾客,写了一份非常精确的菜谱(你的 .py 文件)交给一位名叫 CPython 的大厨(最常见的 Python 解释器)。

  1. 第一步:厨师读懂菜谱(Parsing -> AST)

    • 厨师不会直接逐字朗读菜谱并操作。他会先通读一遍,理解菜谱的结构。比如,“先将土豆切块,然后和牛肉一起炖煮30分钟”。
    • 他会在脑中形成一个清晰的操作流程树:“总任务:炖牛肉” -> “子任务1:准备食材” -> “动作:切土豆”、“动作:切牛肉” -> “子任务2:烹饪” -> “动作:炖煮”。
    • 在 Python 中,这个过程叫做解析(Parsing),生成的这个“操作流程树”叫做抽象语法树(Abstract Syntax Tree, AST)。它把你的代码文本转换成了一个计算机更容易理解的结构化数据。
  2. 第二步:厨师将菜谱翻译成厨房“行话”(Compilation -> Bytecode)

    • 高级餐厅的厨房里,厨师们有自己的“行话”或简写。
    • 我们的 CPython 厨师也会做类似的事。他会把那个复杂的“操作流程树”翻译成一种更低级、更接近机器指令的语言——字节码(Bytecode)
    • 这个字节码就像是厨房的内部指令集,比如 LOAD_CONST 1 (value: '土豆'), CALL_FUNCTION '切', STORE_FAST '切好的土豆'。它比源代码更紧凑,执行起来也更快。
    • 缓存优化:如果这份菜谱(代码)没有修改过,聪明的厨师会把翻译好的“行话”(字节码)写在一个小本本上(存为 .pyc 文件,放在 __pycache__ 文件夹里)。下次你再点同样的菜,他直接拿出小本本,跳过第一步,大大提高效率。
  3. 第三步:厨师助手按“行话”指令操作(Execution by PVM)

    • 最后,有一个严格执行命令的厨房总管,我们称之为 Python 虚拟机(Python Virtual Machine, PVM)
    • PVM 就像一个大循环,它一条一条地读取字节码指令,然后指挥厨房里的各种工具(CPU、内存)去完成实际的操作。比如,它读到 LOAD_CONST 1,就在货架上(内存)找到一个常量;读到 BINARY_ADD,就调用加法器(CPU 的算术逻辑单元)来执行加法。
    • PVM 负责管理所有的资源,包括食材(数据对象)、厨具(内存空间)等。

二、技术细节分解

现在,我们把比喻换成专业术语。当你运行 python my_script.py 时,CPython 解释器内部发生了以下核心步骤:

第 1 步:解析 (Parsing)

解释器首先读取你的 .py 源代码文件。

  • 词法分析 (Lexical Analysis): 将代码文本打碎成一个个有意义的最小单元,称为词法单元 (Token)。例如,x = 10 会被分解为 NAME('x'), EQUAL('='), NUMBER('10')
  • 语法分析 (Syntax Analysis): 根据 Python 的语法规则,将这些词法单元组合成一个树形结构,也就是我们前面提到的抽象语法树 (AST)。这个树精确地表达了代码的逻辑结构。
第 2 步:编译 (Compilation)

接下来,解释器会遍历 AST,并将其“编译”成 Python 特有的字节码 (Bytecode)

  • 什么是字节码? 它是一种平台无关的中间指令集。它不像机器码(0和1)那么底层,但比源代码更接近硬件。你可以把它看作是 PVM 的“汇编语言”。
  • 为什么要这一步? 直接解释 AST 效率很低。编译成字节码是一次性的优化,后续 PVM 执行字节码会快得多。
  • .pyc 文件:编译产生的字节码会被缓存到 __pycache__ 目录下的 .pyc 文件中。如果你的 .py 文件没有被修改,下次运行时 Python 会直接加载这个 .pyc 文件,跳过解析和编译步骤,从而加快启动速度。

你甚至可以自己查看字节码!使用 dis 模块:

import dis

def my_func(a, b):
    return a + b

dis.dis(my_func)

输出会是类似这样的字节码指令。

第 3 步:执行 (Execution)

最后,Python 虚拟机 (PVM) 登场。PVM 是 Python 解释器的核心,它是一个巨大的循环(eval loop),负责:

  1. 读取下一条字节码指令。
  2. 解释这条指令的含义。
  3. 执行对应的 C 函数来完成操作(比如创建对象、进行数学运算、调用函数等)。
  4. 循环直到没有字节码可以执行。

PVM 还负责管理内存(对象的创建、销毁和垃圾回收)和执行栈(跟踪函数调用)。


总结与关键点

  • Python 是解释型语言,但它的主流实现 (CPython) 包含一个编译步骤。 它不是直接解释源代码,而是先编译成字节码,再由虚拟机解释执行字节码。这让它介于纯粹的解释型语言(如 Bash)和纯粹的编译型语言(如 C++)之间。

  • PVM 是跨平台的关键。 只要一个操作系统上有对应的 PVM,你的同一份 Python 字节码(和源代码)就可以在上面运行,无论是 Windows, macOS 还是 Linux。

  • .pyc 文件是启动速度的优化,不是性能优化的全部。 它只节省了解析和编译的时间,代码的实际运行速度取决于 PVM 执行字节码的效率。

  • GIL (全局解释器锁) 是在 PVM 这一层实现的。 在 CPython 中,GIL 保证了在任何时刻,只有一个线程在执行 Python 字节码。这也是为什么 Python 多线程在 CPU 密集型任务上无法利用多核的原因,因为 PVM 的这个限制。

通过理解这个流程,你就掌握了 Python 执行模型的“第一性原理”,很多更高级的概念(如装饰器如何工作、GIL 的影响等)也就有了坚实的基础。

你可能感兴趣的:(Python,python,java,linux)