阅读条件:

C语言基础, Python虚拟机基本了解


前言

Python源码剖析一书的第二部分主要讲述了虚拟机主循环是如何运行的.那么在命令行启动python之后,到执行主循环之前都经历了哪些事情呢?这是我们今天要解决的问题.

另外还要一个思考: 在写python代码的时候,我们经常会写:

1
2
if __name__ == "__main__":
    balabala

这个balabala是如何能运行呢?

入口

在python-2.5.6中,代码入口位于:

1
2
3
4
5
6
7
8
[Modules/main.c]
int Py_Main() {
    ...
    Py_Initialize();
    ...
    PyRun_AnyFileExFlags();
    ...
}

我们要剖析的初始化动作主要在Py_Initialize,而初始化完成后,进入运行时就需要依靠PyRun_AnyFileExFlags.

初始化

Py_Initialize

这个函数我们看到啥也没干,直接进入Py_InitializeEx

Py_InitializeEx

这个函数就厉害了,我们慢慢看.

创建进程结构体

1
2
    // 创建一个interp结构体,空壳
    interp = PyInterpreterState_New();

在python中,使用PyInterpreterState结构体来描述进程.本质上,后面的初始化工作就是在填充这个进程描述结构体.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
typedef struct _is {
    struct _is *next; //链表,链接不同的进程
    struct _ts *tstate_head; // 链表,管理本进程下的线程

    PyObject *modules; //字典, 管理所有的模块
    PyObject *sysdict; //字典, sys module方法
    PyObject *builtins; //字典, 内置方法
    PyObject *modules_reloading;

    PyObject *codec_search_path; // module搜索路径
    PyObject *codec_search_cache;
    PyObject *codec_error_registry;
} PyInterpreterState;

创建线程结构体

1
2
3
4
    // 创建一个thread结构体,挂到interp下
	tstate = PyThreadState_New(interp);
    // 设置当前线程
	(void) PyThreadState_Swap(tstate);

同进程类似,Python使用PyThreadState来描述线程.

1
2
3
4
5
6
7
8
typedef struct _ts {
    struct _ts *next;
    PyInterpreterState *interp;

    struct _frame *frame;
    ....

} PyThreadState;

可以想象,线程结构体必然要挂到进程体之下.同样的在线程结构体中,我们发现了_frame的身影.说明Frameobject又挂在了线程之下.

初始化内置数据结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	_Py_ReadyTypes();

    // 创建了builtin objects: string类型
	if (!_PyFrame_Init())
		Py_FatalError("Py_Initialize: can't init frames");

    // 初始化int类型
	if (!_PyInt_Init())
		Py_FatalError("Py_Initialize: can't init ints");

    // 初始化float类型
	_PyFloat_Init();

这部分内容可以衔接到,我们之前谈到过的内置对象初始的过程中.

设置__builtin__ module

1
2
3
4
    // 并将module添加到interp->modules
	bimod = _PyBuiltin_Init();
    // 从builtin module抽取dict,赋给interp->builtins,加速查找
	interp->builtins = PyModule_GetDict(bimod);

正是这里,我们创建了__builtin__module,并将我们耳熟能详的大量内置方法放了进去.

设置sys module

1
2
3
4
5
    // 设置 sys module
    // 并将module添加到interp->modules
	sysmod = _PySys_Init();
    // interp->sysdict赋值
	interp->sysdict = PyModule_GetDict(sysmod);

可以看到sys module的方法,在初始化的时候,都可以添加完毕.只是我们在自己的名字空间还是看不到他们.

设置搜索路径

1
2
    // 设置搜索路径,即sys.path
	PySys_SetPath(Py_GetPath());

设置__main__ module

1
2
    // 设置__main__ module
	initmain(); /* Module __main__ */

这里我们稍微说下这个__main__,这个在初始化完成后,基本上是个空壳.但是我们后面我们执行过程都在这个名字空间中,这也是我们上面问题的答案.

至此,我们的初始化工作大体完成.

运行

PyRun_AnyFIleExFlags

兵分两路,一个是交互式执行,一个是脚本执行.

1
2
		PyRun_InteractiveLoopFlags(fp, filename, flags); // 交互执行
		PyRun_SimpleFileExFlags(fp, filename, closeit, flags); // 脚本文件执行

交互执行

PyRun_InteractiveOneFlags

本质上,这里就是编译输入的语句.然后交由run_mod执行

脚本执行

PyRun_SimpleFileExFlags

首先,判断是执行pyc文件,还是编译执行py文件.然后调用PyRun_FileExFlags,这个函数也会调用run_mod.

这里我们看到其实执行的真正入口就是run_mod.交互执行和脚本执行殊途同归.

run_mod

PyAst_Compile

根据AST语法树,编译生成python字节码.也就是PyCodeObject.

PyEval_EvalCode

这个函数会调用PyEval_EvalCodeEx.千辛万苦,我们终于走到了我们熟悉的虚拟机主循环.

总结

这里,我们总结下python虚拟机的初始化过程.

首先是准备进程,线程相关结构体.然后初始化一些必备模块,如builtin, sys, __main__.

第二步是编译输入的文件,从而生成PyCodeObject.再之后就是调用执行主逻辑PyEval_EvalCodeEx来运行这个PyCodeObject.

从此之后,python虚拟机就完全运行起来啦.