第一部分:准备工作
在深入源码之前,你需要做好以下准备:

获取 Python 源码
最权威的源码仓库是 GitHub 上的 cpython。
# 克隆最新的 CPython 源码 git clone https://github.com/python/cpython.git
你也可以选择下载特定版本的源码压缩包,这在你研究某个特定 Python 版本时非常有用。
必备的开发环境
- C 编译器: Python 是用 C 语言编写的,所以你需要一个 C 编译器(如 GCC, Clang, MSVC)来编译和调试源码。
- 构建工具: 主要是
make(在 Linux/macOS 上) 或PCBuild(在 Windows 上)。 - 调试器: 强烈推荐 GDB (Linux/macOS) 或 Visual Studio Debugger (Windows),没有调试器,阅读源码会事倍功半。
- Python 环境: 你需要一个已经安装好的 Python 版本来运行
make等构建脚本。
编译和安装 Python 源码
进入 cpython 目录,执行以下步骤:
# 1. 创建一个构建目录 mkdir build cd build # 2. 配置编译选项 (可以不加参数,使用默认配置) ../configure --with-pydebug # --with-pydebug 会增加很多调试信息,比如对象引用计数,强烈推荐! # 3. 编译 # -j 后面跟你的 CPU 核心数,可以加快编译速度 make -j 4 # 4. 安装 (可选,推荐) # 这会创建一个局部的 Python 环境,不会影响你系统自带的 Python make install
编译完成后,你会在 build 目录下得到一个可执行的 python 文件,运行它,你就得到了一个由你自己编译的、可调试的 Python 解释器。

第二部分:阅读源码的核心知识
Python 源码的核心是 CPython,理解以下几个关键概念至关重要:
Python 是如何运行的?
- 源代码 (
.py): 你写的 Python 代码。 - 解释器: 将源代码转换成计算机能理解的指令。
- 字节码 (
.pyc): 解释器将源代码编译成的一种中间、平台无关的指令集,它比源代码更接近机器码,执行效率更高。 - 虚拟机: 一个抽象的计算机,专门用来执行字节码,CPython 的虚拟机通常被称为 PVM (Python Virtual Machine)。
- C 扩展: Python 的很多内置函数和标准库是用 C 语言实现的,它们直接作为 PVM 的指令存在,效率极高。
流程图:
你的代码 (.py) -> 编译器 -> 字节码 (.pyc) -> PVM (解释器核心) -> 机器码
你的 .py 文件首先被编译成字节码,然后由 PVM 逐行执行,理解字节码和 PVM 是理解 Python 运行机制的关键。
关键数据结构
PyObject: 所有 Python 对象的“祖先”,它是一个 C 结构体,包含了对象最核心的信息:引用计数 和 类型指针。typedef struct _object { PyObject_HEAD } PyObject;PyObject_HEAD是一个宏,通常会展开为:
(图片来源网络,侵删)_PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; // 引用计数 struct _typeobject *ob_type; // 指向该对象类型的指针
PyTypeObject: 定义了“类型”本身,int,str,list等,它是一个巨大的结构体,包含了创建该类型对象所需的所有信息,- 对象的大小 (
tp_basicsize) - 析构函数 (
tp_dealloc) - 打印函数 (
tp_print) - 哈希函数 (
tp_hash) - 方法表 (
tp_methods): 这是最重要的部分之一,定义了该类型支持的所有方法(如list.append)。
- 对象的大小 (
引用计数
这是 CPython 内存管理的基石,每个 PyObject 都有一个 ob_refcnt,记录了有多少个地方引用了这个对象。
- 引用增加:
Py_INCREF(obj) - 引用减少:
Py_DECREF(obj) - 当引用计数归零时:
Py_DECREF会发现计数为 0,于是调用该对象的析构函数 (tp_dealloc),释放内存。
注意: CPython 后来引入了 GIL (全局解释器锁) 和 分代垃圾回收 作为引用计数的补充,以处理循环引用等问题。
第三部分:源码结构解读
CPython 的源码目录结构非常清晰:
cpython/
├── Include/ # 所有 C 头文件 (.h),定义了 Python API 和数据结构
│ ├── Python.h # 核心头文件,几乎所有 C 扩展都会包含它
│ └── ...
├── Objects/ # 核心对象的实现,如 int, str, list, dict, function 等
│ ├── intobject.c # int 类型的具体实现
│ ├── listobject.c # list 类型的具体实现
│ └── ...
├── Python/ # 解释器的核心代码,包括字节码编译和 PVM
│ ├── ast.c # 抽象语法树 的构建
│ ├── compile.c # 将 AST 编译成字节码
│ ├── frameobject.c # 帧 对象,即执行栈帧
│ └── ceval.c # **最核心的文件!** PVM 的主循环,执行字节码
├── Modules/ # 标准库中用 C 实现的模块,如 `os`, `sys`, `json` 等
├── Parser/ # Python 词法分析器和语法分析器的实现
├── Grammar/ # Python 语言的语法文件 (`.pgen` 文件)
└── ...
第四部分:实战演练:追踪一个函数的执行
让我们以最简单的 print("Hello") 为例,看看它在源码中是如何一步步执行的。
步骤 1: 字节码层面
我们来看 print("Hello") 被编译成了什么字节码,使用我们编译好的 Python:
# 在 cpython/build 目录下
./python -c "import dis; dis.dis(print('Hello'))"
输出类似这样(版本不同可能略有差异):
1 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Hello')
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
关键指令是 CALL_FUNCTION,CPython 的虚拟机(在 ceval.c 中)就是通过执行这些指令来运行代码的。
步骤 2: C 层面追踪
我们用 GDB 来调试,我们的目标是:当执行 CALL_FUNCTION 时,看看 C 层面发生了什么。
-
准备一个测试文件:
# test.py print("Hello") -
启动 GDB:
# 使用我们编译的 python,并加载调试信息 gdb ./python
-
在 GDB 中设置断点:
(gdb) b ceval.c:PyEval_EvalFrameEx Breakpoint 1 at 0x5555557b5a20: file ceval.c, line 1947. (gdb) r test.py
PyEval_EvalFrameEx是ceval.c中的核心函数,负责执行一个帧对象里的字节码。 -
单步执行: 程序会在
PyEval_EvalFrameEx函数的开头停下,使用n(next) 或s(step) 命令来单步执行,你可以通过x/i $pc查看当前正在执行的 C 指令,或者通过info registers查看寄存器状态。当你执行到
CALL_FUNCTION对应的 C 代码时,你会看到类似这样的逻辑(简化版):- 从操作数栈中弹出函数对象 (
print) 和参数 ('Hello')。 - 检查函数对象的类型,发现它是一个 Python 的 built-in function (BIF)。
- 调用
builtin_print函数(这个函数定义在Modules/main.c或Modules/builtinmodule.c中,具体取决于 Python 版本)。 builtin_print函数接收参数,然后调用 C 的printf或类似函数将 "Hello" 打印到控制台。
- 从操作数栈中弹出函数对象 (
步骤 3: 查找 print 的实现
print 为什么能直接用?因为它是一个内置函数,它的实现就在 Modules/builtinmodule.c 文件中,搜索 PyDoc_STRVAR(print_doc) 和 PyDoc_STRVAR(print_function_docstring),你就能找到 static PyMethodDef builtin_print[] 的定义,这个 PyMethodDef 数组就是 print 函数的 C 实现。
第五部分:探索路径建议
-
从
list开始:list是 Python 中最常用的数据结构之一,它的源码在Objects/listobject.c。- 目标: 理解
list.append是如何工作的。 - 方法: 在
listobject.c中搜索append函数,看看它如何检查边界、如何重新分配内存、如何增加元素,你会发现它操作的是一个 C 数组 (listobject->ob_item)。
- 目标: 理解
-
深入
dict:dict(字典) 的实现非常精妙,从 CPython 3.6 开始,其底层数据结构从“哈希表+冲突链表”改为了“哈希表+冲突开放寻址法”。- 目标: 理解
dict的查找、插入和删除操作。 - 方法: 源码在
Objects/dictobject.c,重点看lookdict函数(查找键)和dictinsert函数(插入键值对),理解PyDictKeyEntry结构和dk_indices数组的作用。
- 目标: 理解
-
理解
int和float: 它们是如何表示的?为什么int在 Python 3 中可以无限大?- 目标: 理解 Python 数值类型的内部表示。
- 方法: 源码在
Objects/longobject.c(Pythonint的实际类型是PyLongObject) 和Objects/floatobject.c,你会发现 Python 的int是一个变长的 C 数组,用来模拟大整数。
-
探索 GIL: GIL 是 CPython 的一个核心特性。
- 目标: 理解 GIL 是什么,它如何工作,以及它带来的影响。
- 方法: 源码在
Python/ceval.c中搜索PyThread_acquire_lock和PyThread_release_lock,你会发现 GIL 的获取和释放是在解释器的主循环中控制的。
第六部分:实用技巧和资源
- 善用搜索:
grep和GitHub的代码搜索是你的好朋友,当你想知道某个函数在哪里定义时,直接搜索函数名。 - 阅读文档: Python 的 Developer's Guide 和 C API Reference 是官方权威资料。
- 社区资源:
- "Writing an Extension Module in C": 这篇官方教程是理解 CPython C API 的最佳起点。
- "Python 内核剖析": 豆瓣上有这本书的中文版,是经典读物。
- PyCon 视频: YouTube 上有很多关于 Python 内部机制的精彩演讲,"The GIL in Depth" 等。
- 不要害怕: 源码量巨大,不可能一次性全部读完,从一个你最感兴趣、最常用的点开始,像剥洋葱一样一层层深入。
保持好奇心和耐心。 每当你对 Python 的某个行为感到困惑时,这恰恰是你深入源码的最好机会,祝你在探索 Python 源码的旅程中玩得开心!
