Python 是一套语法规范,规定了开发者如何编写 Python 的代码。如何解析、执行 Python 的源码,最后输出则是 Python 解释器的职责。我们平时使用的 Python 一般指的是 CPython,其解释器是由 C 语言编写。除此之外,还有比如 Jython, 使用 Java 编写的。Pypy 则是用 Python 写的。

这篇文章描述了如何获取 Python 的源码,源码的目录结构以及如何重新编译 Python。这篇文章使用的 Python 版本是 3.9,如果你想重现文章中的实验,最好是采用相同的版本。
获取源码
我们可以通过 git 来获取 CPython 的源码:
bash
git clone --branch 3.9 <https://github.com/python/cpython>
项目使用的是 IDE 是 JetBrain CLion,开发环境采用的是 macOS。你可以使用任何你熟悉的编辑环境。
源码的目录结构
获取源码之后,我们首先熟悉一下源码的目录结构:
Doc
:文档目录Grammer
:用于定义 Python 语法规则,计算机可以直接读取和处理的文件Include
:C 的头文件(Header files)Lib
:使用 Python 写的标准库模块Mac
:macOS 系统支持的文件Misc
:杂七杂八的文件,没办法分类的文件Modules
:使用 C 语言编写的标准库模块Objects
:核心类型和对象模型Parser
:Python 解析器的源码,语法分析,生成语法树或ASTPC
:用于旧版本的 Windows 系统的构建的支持文件PCbuild
:用于新版本的 Windows 系统的构建的支持文件Programs
:Python 可执行文件和其他二进制文件的源码Python
:CPython 解释器的源码,解释执行 Python 的源码Tools
:用于构建和扩展 CPython 的一些独立的工具m4
: 用于配置 Makefile 的一些自定义脚本
编译 CPython
首先安装编译的依赖:
brew install openssl xz zlib gdbm sqlite
我以 macOS 为例,首先执行 ./configure
检查环境并生成 Makefile
:
ini
$ cpython % CPPFLAGS="-I$(brew --prefix zlib)/include" \
LDFLAGS="-L$(brew --prefix zlib)/lib" \
./configure --with-openssl=$(brew --prefix openssl) --with-pydebug
我来解释一下:
CPPFLAGS
是告诉 C 预处理器,zlib 库的 Header files 的存放的目录;LDFLAGS
是在链接阶段,去$(brew --prefix openssl)
所在目录查找库文件(一般以*.so
或者.a
作为扩展名;--with-pydebug
可以让你在开发或者测试过程中启用调试。
不出意外,你可以看到如下这般输出:
lua
config.status: creating pyconfig.h
creating Modules/Setup.local
creating Makefile
这表明你当前的系统符合编译要求,并且已经生成了编译所需的 Makefile
文件。可以进行编译了。
go
make -j14 -s
这条命令中,-j14
表示通过 14 个任务并行编译(一般等于你的 CPU 核心数就行),-s
全称是 --silent
表示不打印执行的命令本身,让输出更加简洁。
不出意外,你可以看到如下的输出,表明已经编译成功了:
Python build finished successfully!
你会在源码的根目录下看一个
python.exe
的可执行文件。虽然扩展名为.exe
,但这并不是 Windows 下的可执行文件,而是 Python 的开发者特意加上这个扩展名和源码目录下的Python
目录做区分,因为在 macOS 系统中并不区分大小写,所以加上.exe
避免歧义。
你可以执行执行这个可执行文件,输出编译的 Python 的版本:
shell
$ ./python.exe --version
Python 3.9.22+
修改 Python 源码
接下来,我们尝试修改 Python 的源码。为 Python 增加一个关键字。我们知道,Python 中,如果你想暂时不实现一个方法,可以通过 pass
关键字,例如:
python
def do_somethings():
pass
我希望当我输入 pass
或者 ps
都能实现同样的效果。这就需要修改 Python 的语法定义。在 Grammar
目录下,找到 python.gram
文件,并且搜索 small_stmt
关键字,你会看到如下语句:
scss
small_stmt[stmt_ty] (memo):
| assignment
| e=star_expressions { _Py_Expr(e, EXTRA) }
| &'return' return_stmt
| &('import' | 'from') import_stmt
| &'raise' raise_stmt
| 'pass' { _Py_Pass(EXTRA) }
更改如下,将 'pass'
更改为 ('pass' | 'ps')
, 表明 pass
或者 ps
都可以:
scss
- | 'pass' { _Py_Pass(EXTRA) }
+ | ('pass' | 'ps') { _Py_Pass(EXTRA) }
修改完成之后,执行如下命令重新生成语法解析器的表:
bash
$ make regen-pegen
PYTHONPATH=./Tools/peg_generator python3 -m pegen -q c \
./Grammar/python.gram \
./Grammar/Tokens \
-o ./Parser/pegen/parse.new.c
python3 ./Tools/scripts/update_file.py ./Parser/pegen/parse.c ./Parser/pegen/parse.new.c
然后我们重新编译 Python:
go
make clean && make -j14 -s
最后测试一下:

总结
这篇文章描述了如何在 macOS 上编译 CPython 的源码,最终你会得到一个 python.exe
的可执行文件。
在了解了如何编译 Python 解释器之后,我们修改了 Python 的语法规则,增加了一个 ps
的关键字,来实现和 pass
关键字一样的功能,最后编译并测试。