python深入系列(一):python源码运行流程

源码编译

要看懂源码,肯定要学会编译源码。先去官网下载源码文件,然后解压编译安装。linux环境下进解压目录依次运行:

  • ./configure -prefix=<安装路径>
  • make
  • make install

至此一个可运行python就已经编译完成,之后便可以愉快地改源码了。

python运行流程

当我们运行python命令时,代码流向是怎样的呢?makefile中target指向Modules文件夹的python.c,
执行里面的Py_Main方法,Py_Main方法实现在main.c,主要是判断命令参数执行不同操作,整个方法可以简化成初始化环境、执行、清理环境:

Py_Initialize();
sts = PyRun_AnyFileExFlags(
                    fp,
                    filename == NULL ? "" : filename,
                    filename != NULL, &cf) != 0;
            }
Py_Finalize();

python有两种运行模式,命令行模式和文件执行,最终都会调用PyRun_AnyFileFlags方法,参数里面的filename就对应着运行python abc.py这种模式。
PyRun_AnyFileFlags在Python目录的pythonrun.c:

int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);
}

PyRun_InteractiveLoopFlags和PyRun_SimpleFileExFlags分别对应交互式命令行执行和文件执行,但两者最终执行流程会汇合在一起。
我们先来看下Py_Initialize方法,初始化环境初始化了些什么。

python运行环境初始化

Py_Initialize也在pythonrun.c里,实际调用了Py_InitializeEx方法,初始化顺序依次如下:

  • hash算法 随机数初始化
_PyRandom_Init();

这里只是初始化hash算法需要用到的随机数环境,使得 dict 对象中 key 的顺序每次启动时随机;而python的random模块在Lib文件夹的random.py,random.py内部引用的是C生成python模块,位于Modules的_randommodule.c,其中一个随机数基础方法random_random挺有意思,对应random(),把注释也贴出来:

/* random_random is the function named genrand_res53 in the original code;
 * generates a random number on [0,1) with 53-bit resolution; note that
 * 9007199254740992 == 2**53; I assume they're spelling "/2**53" as
 * multiply-by-reciprocal in the (likely vain) hope that the compiler will
 * optimize the division away at compile-time.  67108864 is 2**26.  In
 * effect, a contains 27 random bits shifted left 26, and b fills in the
 * lower 26 bits of the 53-bit numerator.
 * The orginal code credited Isaku Wada for this algorithm, 2002/01/09.
 */
static PyObject *
random_random(RandomObject *self)
{
    unsigned long a=genrand_int32(self)>>5, b=genrand_int32(self)>>6;
    return PyFloat_FromDouble((a*67108864.0+b)*(1.0/9007199254740992.0));
}

PyFloat_FromDouble表示从double类型转换成PyFloat对象,其中genrand_int32方法也是这种魔数风格,感觉很geek。

  • 进程环境初始化
interp = PyInterpreterState_New();

这里只是初始化一个进程状态链表,进程实现在Lib文件夹的multiprocessing和Modules文件夹的_multiprocessing,底层使用的是系统的进程,根据系统不同实现不同

  • 线程环境初始化
tstate = PyThreadState_New(interp);

这里只是初始化一个线程状态链表,线程实现在Modules的threadmodule.c,底层使用的是系统的线程,根据系统不同实现不同

  • 类型初始化
_Py_ReadyTypes();

里面关键的方法PyType_Ready实现在Objects的typeobject.c,根据类型不同进行了初始化

  • int、long等对象初始化
_PyFrame_Init()
_PyInt_Init()
_PyLong_Init()
PyByteArray_Init()
_PyFloat_Init()
_PyUnicode_Init()

除了_PyFrame_Init代表builtin_object,其他方法看名字应该就清楚其用途

  • __builtin__ module初始化
bimod = _PyBuiltin_Init();

点进去就会发现都是些熟悉的面孔

  • sys module初始化
sysmod = _PySys_Init();
  • 初始化import环境
_PyImport_Init();
  • 初始化内建异常
_PyExc_Init();
  • 在sys module中添加一些对象用于import
_PyImportHooks_Init();
  • 初始化信号机制
initsigs();
  • 初始化warning机制
_PyWarnings_Init();
  • 初始化main
 initmain();
  • 这个应该和GIL有关,具体要看下GIL
_PyGILState_Init(interp, tstate);
  • site module初始化
initsite();

就是import Lib文件夹里site.py,目的是加载site-packages里的包,不同平台情况不同

字节码运行

初始完环境,就可以开始执行自己写的代码了。之前提到PyRun_InteractiveLoopFlags和PyRun_SimpleFileExFlags两者最终执行流程会汇合在一起,这里把两者代码简化一下:
PyRun_InteractiveLoopFlags

for (;;) {
        ret = PyRun_InteractiveOneFlags(fp, filename, flags);
        _PyDebug_PrintTotalRefs();
        if (ret == E_EOF)
            return 0;
    }

内部PyRun_InteractiveOneFlags方法

arena = PyArena_New();
mod = PyParser_ASTFromFile(fp, filename,
                               Py_single_input, ps1, ps2,
                               flags, &errcode, arena);
 m = PyImport_AddModule("__main__");
d = PyModule_GetDict(m);
v = run_mod(mod, filename, d, d, flags, arena);
PyArena_Free(arena);

PyRun_SimpleFileExFlags

m = PyImport_AddModule("__main__");
d = PyModule_GetDict(m);
if (maybe_pyc_file(fp, filename, ext, closeit)) {
    v = run_pyc_file(fp, filename, d, d, flags);
} else {
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
}

内部run_pyc_file方法

magic = PyMarshal_ReadLongFromFile(fp);
if (magic != PyImport_GetMagicNumber()) {
    PyErr_SetString(PyExc_RuntimeError,
    "Bad magic number in .pyc file");
    return NULL;
}
(void) PyMarshal_ReadLongFromFile(fp);
v = PyMarshal_ReadLastObjectFromFile(fp);
co = (PyCodeObject *)v;
v = PyEval_EvalCode(co, globals, locals);

内部PyRun_FileExFlags方法

PyArena *arena = PyArena_New();
mod = PyParser_ASTFromFile(fp, filename, start, 0, 0,
                               flags, NULL, arena);
ret = run_mod(mod, filename, globals, locals, flags, arena);
PyArena_Free(arena);

看了代码,可以发现只有两种模式:根据pyc文件执行和调用run_mod方法。pyc概念就不多说了,pyc文件开头会记录magic数,用于兼容python版本问题;而调用run_mod之前的arena是python的内存管理单位,run_mod的代码如下:

co = PyAST_Compile(mod, filename, flags, arena);
v = PyEval_EvalCode(co, globals, locals);

这样所有的执行都汇合到了一起,调用PyEval_EvalCode方法。PyEval_EvalCode接收一个PyCodeObject对象,PyCodeObject对象是python代码编译结果,由此可见pyc文件其实是个PyCodeObject缓存,pyc从名字看也可以看出是简称嘛。PyEval_EvalCode内部调用了PyEval_EvalFrameEx方法,PyEval_EvalFrameEx就是最后的大魔王了。PyEval_EvalFrameEx是个很庞大的方法,还是先从PyCodeObject入手,使用dis模块就可以知道PyCodeObject的结构了,比如print('hello,world')的输出如下:

('const : ', ('hello,world', None))
('name : ', ())
  2           0 LOAD_CONST               0 ('hello,world')
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               1 (None)
              8 RETURN_VALUE        

第一个是静态变量,第二个是变量,第三个是字节码,这个字节码就是执行的核心。PyEval_EvalFrameEx中有个大switch块,里面的case条件就对应着字节码(所有的字节码在这个篇文章可以看到)。

最后的Py_Finalize方法可以看成是Py_Initialize反过程。

本文到这里就结束了,python源码内容丰富庞大,这篇文章只是浅显地过了一下,目的是了解代码执行原理、了解整个脉络,具体细节可以参考《Python源码剖析》这本书。

你可能感兴趣的:(python深入系列(一):python源码运行流程)