什么是 CPython
简单的说, CPython
是 Python 解释器的一个实现。换句话说,Python 解释器是由 C 写的,然后由 GCC 之类的编译器编译而成的可执行文件。除了 CPython,还有 PyPy(用 Python 实现的 Python 解释器),Skulpt(用 JavaScript 实现的 Python 解释器) 等等。如果你不知道你用的是哪一个,那么你一定用的就是 CPython。因为 CPython 是 Python 官方的解释器实现,其它均属于第三方实现。
从源码到运行
对于从 Python 源码到解释器执行它们的这个过程,我把它分为两大步 —— 编译(Compiling) 与 解释(Interpreting)。
编译
尽管 Python 一门典型的解释型语言 —— 与编译型语言(C, C++) 相对,但 Python 的运行确实涉及到了编译的部分。
编译在这里的主要功能是将源代码转换为字节码,包括编译了原理中两个关键步骤,词法分析与语法分析,即 lexing, parsing, 也包含了语法检查,即 SyntaxError
可能在这个过程中抛出。
而由 Python 编译而来的字节码似于如下:
1 | 1 0 LOAD_NAME 0 (print) |
关于字节码的内容,后面将进一步讨论
解释
在这一步,Python 解释器对编译生成的字节码进行解释。且在实际过程中,编译所做的只占很少一部分,也就是说:解释的部分远大于编译的部分 —— 这也是为什么 Python 仍被成为解释型语言的重要原因之一。
因为 Python 解释器对字节码而非源码进行解释,因此 Python 解释器也会被称为 Python 虚拟机(Python Virtual Machine / PVM)。特别需要指出,尽管 Python 虚拟机与 Java 虚拟机都被称为虚拟机,但两者内部之间仍有较大差别(可以参考link)。
字节码的意义
为什么不直接解释源码?
实际上,直接解释源码在理论上当然是可行的。但这样做也有一些缺点。
举个例子:
1 | def test(x, y): |
如果直接解释源码,那么你每次执行 test 函数都要对函数体重新分析,也就要依次分析出 if
语块,if
条件部分。等结构分析好了,之后,才能对 x, y 取值进行比较。
但事实上,代码一旦写好,结构不会再变,会变的只是变量的取值。那么可以先对其编译,编译好后再解释就不需要每次重新分析结构,而是可以直接就对 x, y 进行取值比较。从这个角度讲,编译成字节码提高了解释器的效率。
其次,字节码的存在类似于汇编的存在。汇编介于 C 语言与硬件之间,作为抽象的中间层用于降低开发的复杂度。Python 中的字节码也是如此。
字节码指令集
查看字节码
Python 提高 dis
模块供用户查看由 Python 源码编译而成的字节码。
假设下列代码是 test.py
中的全部内容:
1 | x = 1 |
在终端中输入
1 | python -m dis test.py |
可以看到输出的字节码:
1 | 1 0 LOAD_CONST 0 (1) |
其中每一列代表的含义为:
1 | 行号 字节码偏移量 字节码指令 指令参数 对于参数的相关说明 |
解释字节码
Python 解释器对字节码指令进行解释,同时对 栈 (Stack)进行操作 —- Python 虚拟机属于 栈机器 (Stack machine)。值的存取都是基于栈来实现的。类似下图:
栈机器 优于 寄存器机器(Register Virtual Machine) 的一个地方是不需要对地址的存取,数据的读取通过 POP 和 PUSH 的到,而非通过一个寄存器地址,操作上相对简单。
字节码指令集有哪些
所有的指令码可以在这个网页中看到: https://hg.python.org/cpython/file/v2.7.8/Include/opcode.h。这里以网上资料比较多的 2.7.8 为例。从 0 – 147 共148个指令,每个指令都对应特定的功能。任何 Python 源码编译后形成的字节码都可以在这其中找到。
字节码从哪里被执行
仍以 2.7.8 为例,查看 CPython 工程的 Python/ceval.c 文件: https://hg.python.org/cpython/file/v2.7.8/Python/ceval.c。
第 964 行处有一个 for (;;)
语句块,负责不断读入每一条指令并执行。
继续往下看,第 1112 行有一个“庞大”的 switch
语块。负责检查每一条指令具体是哪一条指令,然后采取对应的操作。
以 1148 行的 POP_TOP
为例:
1 | case POP_TOP: |
对应的操作可描述为: 取并弹出栈顶的数据,对这个数据的计数器减一,执行下一条指令。
CPython 便是以此循环,直到因为用户终止等原因才停止运行。
后续
目前这是一篇极其浅显的对 CPython 的描述。我最近正在学习和研究 CPython 的源码。如果有新的理解,我会继续更新。