以后都在 github 更新,请参考 Cpython Internals
第一步克隆 Cpython 仓库到本地, 切换到我当前的版本, 我当前的版本号是 3.8.0a0
git clone https://github.com/python/cpython.git
git reset --hard ab54b9a130c88f708077c2ef6c4963b632c132b3
PyObject 是 cpython 源码中最基本的 python 对象,记录了单个 Python 对象的任何信息, 他们的 memory layout 如下所示
_PyObject_HEAD_EXTRA 在 Include/object.h 中第 72-83 行, 如果定义了 Py_TRACE_REFS 这个 marco, 他就是一对链表指针,指向上一个对象和下一个对象,不然的话(默认情况下),他就是一个空的值,猜测是 debug 或者其他特殊情况下开启这个选项
ob_refcnt 是引用计数器,类型应该是一个无符号整型, 在cpython中垃圾回收中发挥作用,关于cpython的垃圾回收机制可以参考 python3 的垃圾回收机制
ob_type 是真正指向这个 python 对象的指针,你看到的 tp_name, tp_print, getattr, setattr 等 python 对象共有的属性都能在这里找到
执行一个 .py 文件会经历如下过程
其实 ./python.exe(或者在命令行里敲入 python) 的时候包括了上面的编译器和解释器的全部过程,上图只是做了一个抽象
编译器将 py 文件转换成 py, pyc 里面是 python byte code, 是一个一个的 python 虚拟机指令
比如新建下面的文件 a.py
a = 3
print(a)
之后命令行输入
python3 -m dis a.py
结果:
1 0 LOAD_CONST 0 (3)
2 STORE_NAME 0 (a)
2 4 LOAD_NAME 1 (print)
6 LOAD_NAME 0 (a)
8 CALL_FUNCTION 1
10 POP_TOP
12 LOAD_CONST 1 (None)
14 RETURN_VALUE
左边的 1 和 2 是 第一行和第二行的意思,a.py 总共只有两行代码,所以只能看到 1 和 2
中间的是这一行是每一行代码对应的 python byte code
参考 Include/opcode.h 发现总共有 121 个 opcode, 所有的 python 源文件都会在内存中被编译器翻译成由 opcode 组成字节码指令集, 而 import 目录下会被保存成 pyc 文件,并缓存在执行目录,下次启动程序如果源代码没有修改过,则直接加载这个pyc文件,这个文件的存在可以加快 python 的加载速度
在 python 虚拟机中,解释器主要在一个很大的循环中,不停地读入 opcode, 并根据 opcode 执行对应的指令,当执行完所有指令虚拟机退出,程序也就结束了,这个主要的循环在 ceval.c 中,第 922 行的位置
main_loop:
for (;;) {
...
switch (opcode) {
/* BEWARE!
It is essential that any operation that fails must goto error
and that all operation that succeed call [FAST_]DISPATCH() ! */
case TARGET(NOP): {
FAST_DISPATCH();
}
case TARGET(LOAD_FAST): {
PyObject *value = GETLOCAL(oparg);
if (value == NULL) {
format_exc_check_arg(PyExc_UnboundLocalError,
UNBOUNDLOCAL_ERROR_MSG,
PyTuple_GetItem(co->co_varnames, oparg));
goto error;
}
Py_INCREF(value);
PUSH(value);
FAST_DISPATCH();
}
case TARGET(LOAD_CONST): {
PREDICTED(LOAD_CONST);
PyObject *value = GETITEM(consts, oparg);
Py_INCREF(value);
PUSH(value);
FAST_DISPATCH();
}
...
}
}
可以看到这个循环
我们来简单看下 LOAD_CONST 这个指令, 在上面的 switch case 里面,这个指令的代码总共5行, 第一行和第五行:
PREDICTED(LOAD_CONST);
FAST_DISPATCH();
在 ceval.c 第 732 行可以找到 PREDICTED 的定义, 第 666 行可以找到 FAST_DISPATCH 的定义
#define FAST_DISPATCH() goto fast_next_opcode
...
#define PREDICTED(op) PRED_##op:
## 和 # 号在 marco 里的作用可以参考 这篇
所以 LOAD_CONST 这个指令展开之后如下
case TARGET(LOAD_CONST): {
PRED_LOAD_CONST:
PyObject *value = GETITEM(consts, oparg);
Py_INCREF(value);
PUSH(value);
goto fast_next_opcode;
}
可以看到,这个指令通过 GETITEM 从 oparg 中获取到了一个 python 对象的指针,这个指针的类型是 PyObject *
Py_INCREF 作用是把这个 PyObject * 对象的引用计数器加一, 关于引用计数器可以参考 python3 的垃圾回收机制
PUSH 的作用是把这个刚刚创建的 PyObject * push 到 当前的 frame 的 stack 上面,以便下一个指令从这个 stack 上面获取
关于 frame 可以参考 cpython 源码分析 PyFrameObject