python 源码分析 基本篇

以后都在 github 更新,请参考 Cpython Internals

 

版本

第一步克隆 Cpython 仓库到本地, 切换到我当前的版本, 我当前的版本号是 3.8.0a0

git clone https://github.com/python/cpython.git
git reset --hard ab54b9a130c88f708077c2ef6c4963b632c132b3

PyObject

     PyObject 是 cpython 源码中最基本的 python 对象,记录了单个 Python 对象的任何信息, 他们的 memory layout 如下所示

python 源码分析 基本篇_第1张图片

 

_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 对象共有的属性都能在这里找到

 

Ceval

执行一个 .py 文件会经历如下过程

python 源码分析 基本篇_第2张图片

其实 ./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

你可能感兴趣的:(python,python,internal)