写在前面
如果对 Python 源码感兴趣,那“窥探”其实现的最佳方式就是调教它,不,调试它。
获取源代码
Python 的官方默认实现为 CPython,即 C 语言实现(主要指解释器的实现,其他实现见 Other Interpreter Implementations)。CPython 的源代码可以从官网 pyhton.org 或者 github.com/python/cpython 获取,目前最新的稳定版本为 3.8.0,于 2019.10.14 发布。
这里,从官网 https://www.python.org/downloads/release/python-380/ 下载源码压缩包,如下图所示:
源代码的组织
解压后,目录结构如下
{ Python-3.8.0 } » tree -d -L 1 .
.
├── Doc # rst(reStructuredText)格式官方文档,用其生成https://docs.python.org/
├── Grammar # Python的EBNF(Extended Backus–Naur form)语法定义文件
├── Include # .h 头文件
├── Lib # .py 纯Python实现的标准库
├── m4 # ?
├── Mac # Mac-specific code,支持MacOS
├── Misc # Things that do not belong elsewhere.
├── Modules # C实现的标准库,内含.c .asm .macros .h
├── Objects # 内置数据类型实现
├── Parser # Python语法分析器源码
├── PC # Windows-specific code,支持Windows
├── PCbuild # Windows生成文件,for MSVC
├── Programs # main函数文件,用于生成可执行文件,如python.exe的入口文件
├── Python # CPython解释器源码
└── Tools # 独立工具代码,used to maintain Python
CPython 的源码组织结构如下,摘抄自 CPython Source Code Layout:
源码文件分门别类存放,而且,无论是 py实现的标准库、c实现的标准库、内置数据类型还是内置函数,在Lib/test/和Doc/library/目录下都有与之对应的 test_x.py 测试文件和 rst 文档文件(对于内置数据类型和函数,其文档集中保存在 stdtypes.rst 和 functions.rst)。比如,内置类型int位于Objects/longobject.c文件中。
下面正式开始编译 CPython。
windows下编译CPython
据 Compile and build on Windows,Python3.6 及之后的版本可以使用 VS2017 编译,安装 VS2017 时,记得勾选 Python development 和 Python native development tools,有备无患。
安装好 VS2017 后,双击
PCbuild/pcbuild.sln,打开解决方案。因为我们的关注点仅在 Python 内核和解释器部分,所以仅编译 python和pythoncore,其他模块暂时忽略,具体地,
切换到debug win32
右键解决方案→属性→配置属性
仅勾选项目python和pythoncore
确定
此时再“生成解决方案”,生成目录为PCbuild/win32,内容如下,含解释器 python_d.exe 和内核 python38_d.dll
接下来,将项目 python 设为启动项目(默认状态即是启动项目),点击调试,运行得到如下控制台,可以像平时使用 python 一样,与之交互。
如果想生成全部模块,需要运行PCbuild\get_externals.bat下载依赖,再编译,具体可参见 Build CPython on Windows。
调试CPython
只要程序能运行起来,一切就好办了。凭借“宇宙最强IDE”,我们可以任性地设断点调试甚至修改代码。
F5重新启动调试,弹出控制台。在上面我们知道int类型位于Objects/longobject.c文件,打开文件,简单浏览后在函数PyObject * PyLong_FromLong(long ival)入口处打个断点。
然后,在弹出的控制台中输入a = 1来创建int对象,回车,程序停在了断点处,查看变量ival的值为 1——恰为我们输入的数值,这个函数会跟根据输入的 C long int 创建一个 int 对象,返回对象指针。
再来看看函数调用堆栈,如下图所示:
调用顺序从下至上,从中可以推断出:
从python_d.exe的入口main运行起来后,进入python38_d.dll
从标准输入stdin中读取键入的字符串
解析字符串,建立了语法树AST(abstract syntax tree)
解析语法树中的节点,判断字符为number,将字符串转化为C long int
由C long int创建Python的
int对象
继续运行,弹出的控制台中光标前出现<<
if (!ReadConsoleW(hStdIn, &wbuf[total_read], wbuflen - total_read, &n_read, NULL)) {
err = GetLastError();
goto exit;
}
ReadConsoleW为 WINAPI,详见 ReadConsole function,其等待并读取控制台的输入,读取的字符保存在wbuf中。如果有输入,则进入上面的流程,解析→建立语法树→……
小结
至此,我们揭开了 Python 面纱的一角——不过是一个可运行、可调试的程序而已(微笑)。
参考
Directory structure
reStructuredText
Extended Backus–Naur form
Exploring CPython’s Internals
Compile and build on Windows