python解释器做的事情有哪些_《深度剖析CPython解释器》24. Python运行时环境的初始化、源码分析Python解释器在启动时都做了哪些事情?...

楔子

我们之前分析了Python的核心--字节码、以及虚拟机的剖析工作,但这仅仅只是一部分,而其余的部分则被遮在了幕后。记得我们在分析虚拟机的时候,曾说这么说过:

当Python启动后,首先会进行 "运行时环境" 的初始化,而关于 "运行时环境" 的初始化是一个非常复杂的过程。并且 "运行时环境" 和 "执行环境" 是不同的, "运行时环境" 是一个全局的概念,而 "执行环境" 是一个栈帧。关于"运行时环境"我们后面将用单独的一章进行剖析,这里就假设初始化动作已经完成,我们已经站在了Python虚拟机的门槛外面,只需要轻轻推动一下第一张骨牌,整个执行过程就像多米诺骨牌一样,一环扣一环地展开。

所以这次,我们将回到时间的起点,从Python的应用程序被执行开始,一步一步紧紧跟随Python的轨迹,完整地展示Python在启动之初的所有动作。当我们根据Python完成所有的初始化动作之后,也就能对Python执行引擎执行字节码指令时的整个运行环境了如执掌了。

线程环境初始化

我们知道线程是操作系统调度的最小单元,那么Python中的线程又是怎么样的呢?

线程模型

我们之前介绍栈帧的时候说过,通过Python启动一个线程,那么底层会通过C来启动一个线程,然后启动操作系统的一个原生线程(OS线程)。所以Python中的线程实际上是对OS线程的一个封装,因此Python中的线程是货真价实的。

然后Python还提供了一个PyThreadState(线程状态)对象,维护OS线程执行的状态信息,相当于是OS线程的一个抽象描述。虽然真正用来执行的线程及其状态肯定是由操作系统进行维护的,但是Python虚拟机在运行的时候总需要另外一些与线程相关的状态和信息,比如是否发生了异常等等,这些信息显然操作系统是没有办法提供的。而PyThreadState对象正是Python为OS线程准备的、在虚拟机层面保存其状态信息的对象,也就是线程状态对象。而在Python中,当前活动的OS线程对应的PyThreadState对象可以通过PyThreadState_GET获得,有了线程状态对象之后,就可以设置一些额外信息了。具体内容,我们后面会说。

当然除了线程状态对象之外,还有进程状态对象,我们来看看两者在Python底层的定义是什么?它们位于 Include/pystate.h 中。

typedef struct _is PyInterpreterState;

typedef struct _ts PyThreadState;

里面的 PyInterpreterState 表示进程状态对象, PyThreadState 表示线程状态对象。但是我们看到它们都是typedef起得一个别名,而定义的结构体 struct _is 位于 Include/cpython/pystate.h 中, struct _ts 位于 Include/internal/pycore_pystate.h中。

线程状态对象:

struct _ts {

struct _ts *prev; //多个线程状态对象也像链表一样串起来, 因为一个进程里面是可以包含多个线程的, prev指向上一个线程状态对象

struct _ts *next; //指向下一个线程状态对象

PyInterpreterState *interp; //进程状态对象, 标识对应的线程是属于哪一个进程的

struct _frame *frame; //栈帧对象, 模拟线程中函数的调用堆栈

int recursion_depth; //递归深度

//.....

//.....

//....

uint64_t id; //线程id

};

进程状态对象:

struct _is {

struct _is *next; //当前进程的下一个进程

struct _ts *tstate_head; //进程环境中的线程状态对象的集合, 我们说线程状态对象会形成一个链表, 这里就是链表的头结点

int64_t id; //线程id

//....

PyObject *audit_hooks;

};

我们说 PyInterpreterState 对象是对进程的模拟, PyThreadState 是对线程的模拟。我们之前分析虚拟机的时候说过其执行环境,如果再将运行时环境加进去的话。

1229382-20200909213714167-397884335.png

线程环境的初始化

在Python启动之后,初始化的动作是从 Py_NewInterpreter 函数开始的,然后这个函数调用了 new_interpreter 函数完成初始化,我们分析会先从 new_interpreter 函数开始,当然 Py_NewInterpreter 里面也做了一些工作,具体的后面会说。

我们知道在Windows平台上,当执行一个可执行文件时,操作系统首先创建一个进程内核。同理在Python中亦是如此,会在 new_interpreter 中调用 PyInterpreterState_New 创建一个崭新的 PyInterpreterState对象。该函数位于 Python/pystate.c 中。

PyInterpreterState *

PyInterpreterState_New(void)

{

//申请进程状态对象所需要的内存

PyInterpreterState *interp = PyMem_RawMalloc(sizeof(PyInterpreterState));

if (interp == NULL) {

return NULL;

}

//设置属性

//......

//......

return interp;

}

关于进程状态对象我们不做过多解释,只需要知道Python解释器在启动时,会创建一个、或者多个 PyInterpreterState 对象,然后通过内部的next指针将多个 PyInterpreterState 串成一个链表结构。

在调用 PyInterpreterState_New 成功创建 PyInterpreterState之后,会再接再厉,调用 PyThreadState_New 创建一个全新的进程状态对象,相关函数定义同样位于 Python/pystate.c 中。

PyThreadState *

PyThreadState_New(PyInterpreterState *interp)

{

//我们注意到这个函数接收一个PyInterpreterState

//这些说明了线程是依赖于进程的,因为需要进程分配资源,而且这个函数又调用了new_threadstate

//除了传递PyInterpreterState之外,还传了一个1,想也不用想肯定是创建的线程数量

//这里创建1个,也就是主线程(main thread)

return new_threadstate(interp, 1);

}

static PyThreadState *

new_threadstate(PyInterpreterState *interp, int init)

{

_PyRuntimeState *runtime = &_PyRuntime;

//为线程状态对象申请内存

PyThreadState *tstate = (PyThreadState *)PyMem_RawMalloc(sizeof(PyThreadState));

if (tstate == NULL) {

return NULL;

}

//设置从线程中获取函数调用栈的操作

if (_PyThreadState_GetFrame == NULL) {

_PyThreadState_GetFrame = threadstate_getframe;

}

//设置该线程所在的进程

tstate->interp = interp;

//下面就是设置内部的成员属性

tstate->frame = NULL; //栈帧

tstate->recursion_depth = 0; //递归深度

tstate->id = ++interp->tstate_next_unique_id;//线程id

//......

//......

//......

tstate->prev = NULL; //上一个线程状态对象

tstate->next = interp->tstate_head;//当前线程状态对象的next, 我们看到指向了线程状态对象链表的头结点, 说明是头插法

if (tstate->next)

//因为每个线程状态对象的prev指针都要指向它的上一个线程状态对象, 如果是头结点的话, 那么prev就指向NULL

//但由于新的线程状态对象在插入之后显然就变成了链表的头结点, 因此还需要将插入之间的头结点的prev指向新插入的线程状态对象

tstate->next->prev = tstate;

//将tstate_head设置为新的线程状态对象(链表的头结点)

interp->tstate_head = tstate;

//返回线程状态对象

return tstate;

}

和 PyInterpreterState_New 相同, PyThreadState_New 申请内存,创建 PyThreadState 对象,并且对其中每个成员进行初始化。而且其中的prev指针和next指针分别指向了上一个线程状态对象和下一个线程状态对象。而且也肯定会存在某一时刻,存在多个 PyThreadState 对象形成一个链表,那么什么时刻会发生这种情况呢?显然用鼻子想也知道这是在Python启动多线程(下一章分析)的时候。

此外我们看到Python在插入线程状态对象的时候采用的是头插法。

我们说Python设置了从线程中获取函数调用栈的操作,所谓函数调用栈就是我们前面章节说的PyFrameObject对象链表。而且在源码中,我们看到了 PyThreadState 关联了 PyInterpreterState , PyInterpreterState 也关联了 PyInterpreterState 。到目前为止,仅有的两个对象建立起了联系。对应到Windows,或者说操作系统,我们说进程和线程建立了联系

在 PyInterpreterState 和 PyThreadState 建立了联系之后,那么就很容易在 PyInterpreterState 和PyThreadState 之间穿梭。并且在Python运行时环境中,会有一个变量(先买个关子)一直维护着当前活动的线程,更准确的说是当前活动线程(OS线程)对应的 PyThreadState 对象。初始时,该变量为NULL。在Python启动之后创建了第一个 PyThreadState 之后,会用该 PyThreadState 对象调用 PyThreadState_Swap 函数来设置这个变量,函数位于 Python/pystate.c 中。

PyThreadState *

PyThreadState_Swap(PyThreadState *newts)

{

//调用了_PyThreadState_Swap, 里面传入了两个参数, 第一个我们后面说, 显然从名字上看我们知道这是个GIL相关的

//第二个参数就是创建的线程状态对象

return _PyThreadState_Swap(&_PyRuntime.gilstate, newts);

}

PyThreadState *

_PyThreadState_Swap(struct _gilstate_runtime_state *gilstate, PyThreadState *newts)

{

//这里是获取当前的线程状态对象, 并且保证线程的安全性

PyThreadState *oldts = _PyRuntimeGILState_GetThreadState(gilstate);

//将GIL交给newts

_PyRuntimeGILState_SetThreadState(gilstate, newts);

//....

return oldts;

}

//通过&(gilstate)->tstate_current获取当前线程

#define _PyRuntimeGILState_GetThreadState(gilstate) \

((PyThreadState*)_Py_atomic_load_relaxed(&(gilstate)->tstate_current))

//将newts设置为当前线程, 可以理解为发生了线程的切换

#define _PyRuntimeGILState_SetThreadState(gilstate, value) \

_Py_atomic_store_relaxed(&(gilstate)->tstate_current, \

(uintptr_t)(value))

然后我们看到这两个宏里面出现了 _Py_atomic_load_relaxed 、 _Py_atomic_store_relaxed 和 &(gilstate)->tstate_current ,这些又是什么呢?还有到底哪个变量在维护这当前的活动线程对应的状态对象呢?其实那两个宏已经告诉你了。

//Include/internal/pycore_pystate.h

struct _gilstate_runtime_state {

//...

//宏里面出现的gilstate就是该结构体实例, tstate_current指的就是当前活动的OS线程对应的状态对象

//同时也是获取到GIL的Python线程

_Py_atomic_address tstate_current;

//...

};

//Include/internal/pycore_atomic.h

#define _Py_atomic_load_relaxed(ATOMIC_VAL) \

_Py_atomic_load_explicit((ATOMIC_VAL), _Py_memory_order_relaxed)

#define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL) \

_Py_atomic_store_explicit((ATOMIC_VAL), (NEW_VAL), _Py_memory_order_relaxed)

#define _Py_atomic_load_explicit(ATOMIC_VAL, ORDER) \

atomic_load_explicit(&((ATOMIC_VAL)->_value), ORDER)

#define _Py_atomic_store_explicit(ATOMIC_VAL, NEW_VAL, ORDER) \

atomic_store_explicit(&((ATOMIC_VAL)->_value), NEW_VAL, ORDER)

//_Py_atomic_load_relaxed用到了_Py_atomic_load_explicit, _Py_atomic_load_explicit用到了atomic_load_explicit

//_Py_atomic_store_relaxed用到了_Py_atomic_store_explicit, _Py_atomic_store_explicit用到了atomic_store_explicit

//而atomic_load_explicit和atomic_store_explicit是系统头文件stdatomic.h中定义的api,这是在系统的api中修改的,所以说是线程安全的

介绍完中间部分的内容,那么我们可以从头开始分析Python运行时的初始化了,我们说它是在 new_interpreter 函数中调用 _PyRuntime_Initialize 函数时开始的,函数位于 Python/pylifecycle.c 中。

PyThreadState *

Py_NewInterpreter(void)

{

//线程状态对象

PyThreadState *tstate = NULL;

//传入线程对象, 调用new_interpreter

PyStatus status = new_interpreter(&tstate);

//异常检测

if (_PyStatus_EXCEPTION(status)) {

Py_ExitStatusException(status);

}

//返回线程状态对象, 显然不会返回一个NULL, 这就说明在new_interpreter中线程状态对象就已经被设置了

return tstate;

}

另外里面出现了一个 PyStatus, 表示程序执行的状态, 会检测是否发生了异常,该结构体定义在 Include/cpython/initconfig.h 中。

typedef struct {

enum {

_PyStatus_TYPE_OK=0,

_PyStatus_TYPE_ERROR=1,

_PyStatus_TYPE_EXIT=2

} _type;

const char *func;

const char *err_msg;

int exitcode;

} PyStatus;

然后我们的重点是 new_interpreter函数,我们进程状态对象的创建就是在这个函数里面发生的,该函数位于Python/pylifecycle.c中。

static PyStatus

new_interpreter(PyThreadState **tstate_p)

{

PyStatus status; //状态对象

//运行时初始化, 如果出现异常直接返回

status = _PyRuntime_Initialize();

if (_PyStatus_EXCEPTION(status)) {

return status;

}

//......

// 创建一个进程状态对象

PyInterpreterState *interp = PyInterpreterState_New();

//......

//根据进程状态对象创建一个线程状态对象, 维护对应OS线程的状态

PyThreadState *tstate = PyThreadState_New(interp);

//将GIL的控制权交给创建的线程

PyThreadState *save_tstate = PyThreadState_Swap(tstate);

//...

}

Python在初始化运行时环境时,肯定也要对类型系统进行初始化等等,整体是一个非常庞大的过程。有兴趣的话,可以追根溯源对着源码阅读以下。

到这里,我们对 new_interpreter 算是有了一个阶段性的成功,我们创建了代表进程和线程概念的 PyInterpreterState 和 PyThreadState 对象,并且在它们之间建立的联系。下面, new_interpreter 将进行入另一个环节,设置系统module。

创建__builtins__

在 new_interpreter 中当Python解释器创建了 PyInterpreterState 和 PyThreadState 对象之后,就会开始设置系统的__builtins__了。

static PyStatus

new_interpreter(PyThreadState **tstate_p)

{

//....

//申请一个PyDictObject对象, 用于存储所有的module对象

//而我们说Python中的module对象都是存在sys.modules中的, 所以这里的modules指的就是Python中的sys.modules

PyObject *modules = PyDict_New();

if (modules == NULL) {

return _PyStatus_ERR("can't make modules dictionary");

}

//然后让interp -> modules维护modules

//我们翻看到这个interp表示的时进程实例对象, 这说明什么? 显然是该进程内的多个线程共享同一个内置名字空间

interp->modules = modules;

//加载sys模块, 我们说所有的module对象都在sys.modules中

PyObject *sysmod = _PyImport_FindBuiltin("sys", modules);

if (sysmod != NULL) {

interp->sysdict = PyModule_GetDict(sysmod);

if (interp->sysdict == NULL) {

goto handle_error;

}

Py_INCREF(interp->sysdict);

PyDict_SetItemString(interp->sysdict, "modules", modules);

if (_PySys_InitMain(runtime, interp) < 0) {

return _PyStatus_ERR("can't finish initializing sys");

}

}

//加载内置模块, builtins是内置模块, 可以import builtins, 并且builtins.list等价于list

PyObject *bimod = _PyImport_FindBuiltin("builtins", modules);

if (bimod != NULL) {

interp->builtins = PyModule_GetDict(bimod);

if (interp->builtins == NULL)

goto handle_error;

Py_INCREF(interp->builtins);

}

//......

}

整体还是比较清晰和直观的,另外我们说内置名字空间是由进程来维护的,因为进程就是用来为线程提供资源的。但是我们也能看出,这意味着一个进程内的多个线程共享同一个内置作用域,显然这是非常合理的,不可能每开启一个线程,就为其创建一个__builtins__。我们来从Python的角度证明这一点:

import threading

import builtins

def foo1():

builtins.list, builtins.tuple = builtins.tuple, builtins.list

def foo2():

print(f"猜猜下面代码会输出什么:")

print("list:", list([1, 2, 3, 4, 5]))

print("tuple:", tuple([1, 2, 3, 4, 5]))

f1 = threading.Thread(target=foo1)

f1.start()

f1.join()

threading.Thread(target=foo2).start()

"""

猜猜下面代码会输出什么:

list: (1, 2, 3, 4, 5)

tuple: [1, 2, 3, 4, 5]

"""

我们说所有的内建对象和内置函数都在内置名字空间里面,我们可以通过 import builtins获取、也可以直接通过__builtins__这个变量来获取。我们在foo1中把list和tuple互换了,而这个结果显然也影响到了foo2函数。这也说明了__builtins__是属于进程级别的,它是被多个线程共享的。所以是interp -> modules = modules,当然这个modules是sys.modules,因为不止内置名字空间,所有的module对象都是被多个线程共享的。

而对__builts__的初始化时在 _PyBuiltin_Init 函数中进行的,它位于 Python/bltinmodule.c 中。

PyObject *

_PyBuiltin_Init(void)

{

PyObject *mod, *dict, *debug;

const PyConfig *config = &_PyInterpreterState_GET_UNSAFE()->config;

if (PyType_Ready(&PyFilter_Type) < 0 ||

PyType_Ready(&PyMap_Type) < 0 ||

PyType_Ready(&PyZip_Type) < 0)

return NULL;

//创建并设置__builtins__ module

mod = _PyModule_CreateInitialized(&builtinsmodule, PYTHON_API_VERSION);

if (mod == NULL)

return NULL;

//将所有python内建对象加入到__builtins__ module中

dict = PyModule_GetDict(mod);

//......

//老铁们,下面这些东西应该不陌生吧

SETBUILTIN("None", Py_None);

SETBUILTIN("Ellipsis", Py_Ellipsis);

SETBUILTIN("NotImplemented", Py_NotImplemented);

SETBUILTIN("False", Py_False);

SETBUILTIN("True", Py_True);

SETBUILTIN("bool", &PyBool_Type);

SETBUILTIN("memoryview", &PyMemoryView_Type);

SETBUILTIN("bytearray", &PyByteArray_Type);

SETBUILTIN("bytes", &PyBytes_Type);

SETBUILTIN("classmethod", &PyClassMethod_Type);

SETBUILTIN("complex", &PyComplex_Type);

SETBUILTIN("dict", &PyDict_Type);

SETBUILTIN("enumerate", &PyEnum_Type);

SETBUILTIN("filter", &PyFilter_Type);

SETBUILTIN("float", &PyFloat_Type);

SETBUILTIN("frozenset", &PyFrozenSet_Type);

SETBUILTIN("property", &PyProperty_Type);

SETBUILTIN("int", &PyLong_Type);

SETBUILTIN("list", &PyList_Type);

SETBUILTIN("map", &PyMap_Type);

SETBUILTIN("object", &PyBaseObject_Type);

SETBUILTIN("range", &PyRange_Type);

SETBUILTIN("reversed", &PyReversed_Type);

SETBUILTIN("set", &PySet_Type);

SETBUILTIN("slice", &PySlice_Type);

SETBUILTIN("staticmethod", &PyStaticMethod_Type);

SETBUILTIN("str", &PyUnicode_Type);

SETBUILTIN("super", &PySuper_Type);

SETBUILTIN("tuple", &PyTuple_Type);

SETBUILTIN("type", &PyType_Type);

SETBUILTIN("zip", &PyZip_Type);

debug = PyBool_FromLong(config->optimization_level == 0);

if (PyDict_SetItemString(dict, "__debug__", debug) < 0) {

Py_DECREF(debug);

return NULL;

}

Py_DECREF(debug);

return mod;

#undef ADD_TO_ALL

#undef SETBUILTIN

}

整个 _PyBuiltin__Init 函数的功能就是设置好__builtins__ module,而这个过程是分为两步的。

通过_PyModule_CreateInitialized函数创建PyModuleObject对象,我们知道这是Python中模块对象的底层实现;

设置module,将python中所有的内建对象都塞到__builtins__中

但是我们看到设置的东西似乎少了不少,比如dir、hasattr、setattr等等,这些明显也是内置的,但是它们到哪里去了。别急,我们刚才说创建__builtins__分为两步,第一步是创建PyModuleObject,而使用的函数就是 _PyModule_CreateInitialized ,而在这个函数里面就已经完成了大部分设置__builtins__的工作。该函数位于 Object/moduleobject.c 。

PyObject *

_PyModule_CreateInitialized(struct PyModuleDef* module, int module_api_version)

{

const char* name;

PyModuleObject *m;

//初始化

if (!PyModuleDef_Init(module))

return NULL;

//拿到module的name,对于当前来说,这里显然是__builtins__

name = module->m_name;

//这里比较有意思,这是检测模块版本的,针对的是需要导入的py文件。

//我们说编译成PyCodeObject对象之后,会直接从当前目录的__pycache__里面导入

//而那里面都是pyc文件,介绍字节码的时候我们说,pyc文件的文件名是有Python解释器的版本号的

//这里就是比较版本是否一致,不一致则不导入pyc文件,而是会重新编译py文件

if (!check_api_version(name, module_api_version)) {

return NULL;

}

if (module->m_slots) {

PyErr_Format(

PyExc_SystemError,

"module %s: PyModule_Create is incompatible with m_slots", name);

return NULL;

}

//创建一个PyModuleObject

if ((m = (PyModuleObject*)PyModule_New(name)) == NULL)

return NULL;

//.......

if (module->m_methods != NULL) {

//遍历methods中指定的module对象中应包含的操作集合

if (PyModule_AddFunctions((PyObject *) m, module->m_methods) != 0) {

Py_DECREF(m);

return NULL;

}

}

if (module->m_doc != NULL) {

//设置docstring

if (PyModule_SetDocString((PyObject *) m, module->m_doc) != 0) {

Py_DECREF(m);

return NULL;

}

}

m->md_def = module;

return (PyObject*)m;

}

根据上面的代码我们可以得出如下信息:

1. name:module对象的名称,在这里就是__builtins__

2. module_api_version:python内部使用的version值,用于比较

3. PyModule_New:用于创建一个PyModuleObject对象

4. methods:该module中所包含的函数的集合,在这里是builtin_methods

5. PyModule_AddFunctions:设置methods中的函数操作

6. PyModule_SetDocString:设置docstring

创建module对象

我们说Python中的module对象在底层cpython中对应的结构体是PyModuleObject对象,我们来看看它长什么样子吧,定义在 Objects/moduleobject.c 中。

typedef struct {

PyObject_HEAD //头部信息

PyObject *md_dict; //属性字典, 所有的属性和值都在里面

struct PyModuleDef *md_def; //module对象包含的操作集合, 里面是一些结构体, 每个结构体包含一个函数的相关信息

//...

PyObject *md_name; //模块名

} PyModuleObject;

而这个对象我们知道是通过PyModule_New创建的。

PyObject *

PyModule_New(const char *name)

{

//module对象的name、PyModuleObject

PyObject *nameobj, *module;

nameobj = PyUnicode_FromString(name);

if (nameobj == NULL)

return NULL;

//根据创建PyModuleObject

module = PyModule_NewObject(nameobj);

Py_DECREF(nameobj);

return module;

}

PyObject *

PyModule_NewObject(PyObject *name)

{

//创建一个module对象

PyModuleObject *m;

//申请空间

m = PyObject_GC_New(PyModuleObject, &PyModule_Type);

if (m == NULL)

return NULL;

//设置相应属性, 初始化为NULL

m->md_def = NULL;

m->md_state = NULL;

m->md_weaklist = NULL;

m->md_name = NULL;

//属性字典

m->md_dict = PyDict_New();

//调用module_init_dict

if (module_init_dict(m, m->md_dict, name, NULL) != 0)

goto fail;

PyObject_GC_Track(m);

return (PyObject *)m;

fail:

Py_DECREF(m);

return NULL;

}

static int

module_init_dict(PyModuleObject *mod, PyObject *md_dict,

PyObject *name, PyObject *doc)

{

_Py_IDENTIFIER(__name__);

_Py_IDENTIFIER(__doc__);

_Py_IDENTIFIER(__package__);

_Py_IDENTIFIER(__loader__);

_Py_IDENTIFIER(__spec__);

if (md_dict == NULL)

return -1;

if (doc == NULL)

doc = Py_None;

//模块的一些属性、__name__、__doc__等等

if (_PyDict_SetItemId(md_dict, &PyId___name__, name) != 0)

return -1;

if (_PyDict_SetItemId(md_dict, &PyId___doc__, doc) != 0)

return -1;

if (_PyDict_SetItemId(md_dict, &PyId___package__, Py_None) != 0)

return -1;

if (_PyDict_SetItemId(md_dict, &PyId___loader__, Py_None) != 0)

return -1;

if (_PyDict_SetItemId(md_dict, &PyId___spec__, Py_None) != 0)

return -1;

if (PyUnicode_CheckExact(name)) {

Py_INCREF(name);

Py_XSETREF(mod->md_name, name);

}

return 0;

}

这里虽然创建了一个module对象,但是这仅仅是一个空的module对象,却并没有包含相应的操作和数据。我们看到只设置了name和doc等属性。

设置module对象

在PyModule_New结束之后,程序继续执行 _PyModule_CreateInitialized 下面的代码,然后我们知道通过 PyModule_AddFunctions 完成了对__builtins__几乎全部属性的设置。这个设置的属性依赖于第二个参数methods,在这里为builtin_methods。然后会遍历builtin_methods,并处理每一项元素,我们还是来看看长什么样子。

//Python/bltinmodule.c

static PyMethodDef builtin_methods[] = {

{"__build_class__", (PyCFunction)(void(*)(void))builtin___build_class__,

METH_FASTCALL | METH_KEYWORDS, build_class_doc},

{"__import__", (PyCFunction)(void(*)(void))builtin___import__, METH_VARARGS | METH_KEYWORDS, import_doc},

BUILTIN_ABS_METHODDEF

BUILTIN_ALL_METHODDEF

BUILTIN_ANY_METHODDEF

BUILTIN_ASCII_METHODDEF

BUILTIN_BIN_METHODDEF

{"breakpoint", (PyCFunction)(void(*)(void))builtin_breakpoint, METH_FASTCALL | METH_KEYWORDS, breakpoint_doc},

BUILTIN_CALLABLE_METHODDEF

BUILTIN_CHR_METHODDEF

BUILTIN_COMPILE_METHODDEF

BUILTIN_DELATTR_METHODDEF

{"dir", builtin_dir, METH_VARARGS, dir_doc},

BUILTIN_DIVMOD_METHODDEF

BUILTIN_EVAL_METHODDEF

BUILTIN_EXEC_METHODDEF

BUILTIN_FORMAT_METHODDEF

{"getattr", (PyCFunction)(void(*)(void))builtin_getattr, METH_FASTCALL, getattr_doc},

BUILTIN_GLOBALS_METHODDEF

BUILTIN_HASATTR_METHODDEF

BUILTIN_HASH_METHODDEF

BUILTIN_HEX_METHODDEF

BUILTIN_ID_METHODDEF

BUILTIN_INPUT_METHODDEF

BUILTIN_ISINSTANCE_METHODDEF

BUILTIN_ISSUBCLASS_METHODDEF

{"iter", (PyCFunction)(void(*)(void))builtin_iter, METH_FASTCALL, iter_doc},

BUILTIN_LEN_METHODDEF

BUILTIN_LOCALS_METHODDEF

{"max", (PyCFunction)(void(*)(void))builtin_max, METH_VARARGS | METH_KEYWORDS, max_doc},

{"min", (PyCFunction)(void(*)(void))builtin_min, METH_VARARGS | METH_KEYWORDS, min_doc},

{"next", (PyCFunction)(void(*)(void))builtin_next, METH_FASTCALL, next_doc},

BUILTIN_OCT_METHODDEF

BUILTIN_ORD_METHODDEF

BUILTIN_POW_METHODDEF

{"print", (PyCFunction)(void(*)(void))builtin_print, METH_FASTCALL | METH_KEYWORDS, print_doc},

BUILTIN_REPR_METHODDEF

BUILTIN_ROUND_METHODDEF

BUILTIN_SETATTR_METHODDEF

BUILTIN_SORTED_METHODDEF

BUILTIN_SUM_METHODDEF

{"vars", builtin_vars, METH_VARARGS, vars_doc},

{NULL, NULL},

};

怎么样,是不是看到了玄机。

总结一下就是:在 Py_NewInterpreter 中调用 new_interpreter 函数,然后在 new_interpreter 这个函数里面,通过 PyInterpreterState_New 创建 PyInterpreterState ,然后传递 PyInterpreterState 调用 PyThreadState_New 得到 PyThreadState 对象。

接着就是执行各种初始化动作,然后在 new_interpreter 中调用 _PyBuiltin_Init 设置内建属性,在代码的最后会设置大量的内置属性(函数、对象)。但是有几个却不在里面,比如:dir、getattr等等。所以中间调用的 _PyModule_CreateInitialized 不仅仅是初始化一个module对象,还会在初始化之后将我们没有看到的一些属性设置进去,在 _PyModule_CreateInitialized 里面,先是使用 PyModule_New 创建一个PyModuleObject,在里面设置了name和doc等属性之后,再通过 PyModule_AddFunctions 设置methods,在这里面我们看到了dir、getattr等内置属性。当这些属性设置完之后,退回到 _PyBuiltin_Init 函数中,再设置剩余的大量属性。之后,__builtins__就完成了。

另外 builtin_methods 是一个 PyMethodDef 类型的数组,里面是一个个的 PyMethodDef 结构体,而这个结构体定义在 Include/methodobject.h 中。

struct PyMethodDef {

/* 内置的函数或者方法名 */

const char *ml_name;

/* 实现对应逻辑的C函数,但是需要转成PyCFunction类型,主要是为了更好的处理关键字参数 */

PyCFunction ml_meth;

/* 参数类型

#define METH_VARARGS 0x0001 扩展位置参数

#define METH_KEYWORDS 0x0002 扩展关键字参数

#define METH_NOARGS 0x0004 不需要参数

#define METH_O 0x0008 需要一个参数

#define METH_CLASS 0x0010 被classmethod装饰

#define METH_STATIC 0x0020 被staticmethod装饰

*/

int ml_flags;

//函数的__dic__

const char *ml_doc;

};

typedef struct PyMethodDef PyMethodDef;

对于这里面每一个 PyMethodDef ,_PyModule_CreateInitialized 都会基于它创建一个 PyCFunctionObject 对象, 这个对象Python对函数指针的包装, 当然里面好包含了其它信息。

typedef struct {

PyObject_HEAD //头部信息

PyMethodDef *m_ml; //PyMethodDef

PyObject *m_self; //self参数

PyObject *m_module; //__module__属性

PyObject *m_weakreflist; //弱引用列表, 不讨论

vectorcallfunc vectorcall;

} PyCFunctionObject;

而 PyCFunctionObject 对象则是通过 PyCFunction_New 完成的,该函数位于 Objects/methodobject.c 中。

PyObject *

PyCFunction_New(PyMethodDef *ml, PyObject *self)

{

return PyCFunction_NewEx(ml, self, NULL);

}

PyObject *

PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)

{

vectorcallfunc vectorcall;

//判断参数类型

switch (ml->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS))

{

case METH_VARARGS:

case METH_VARARGS | METH_KEYWORDS:

vectorcall = NULL;

break;

case METH_FASTCALL:

vectorcall = cfunction_vectorcall_FASTCALL;

break;

case METH_FASTCALL | METH_KEYWORDS:

vectorcall = cfunction_vectorcall_FASTCALL_KEYWORDS;

break;

case METH_NOARGS:

vectorcall = cfunction_vectorcall_NOARGS;

break;

case METH_O:

vectorcall = cfunction_vectorcall_O;

break;

default:

PyErr_Format(PyExc_SystemError,

"%s() method: bad call flags", ml->ml_name);

return NULL;

}

PyCFunctionObject *op;

//我们看到这里也采用了缓存池的策略

op = free_list;

if (op != NULL) {

free_list = (PyCFunctionObject *)(op->m_self);

(void)PyObject_INIT(op, &PyCFunction_Type);

numfree--;

}

else {

//否则重新申请

op = PyObject_GC_New(PyCFunctionObject, &PyCFunction_Type);

if (op == NULL)

return NULL;

}

//设置属性

op->m_weakreflist = NULL;

op->m_ml = ml;

Py_XINCREF(self);

op->m_self = self;

Py_XINCREF(module);

op->m_module = module;

op->vectorcall = vectorcall;

_PyObject_GC_TRACK(op);

return (PyObject *)op;

}

在 _PyBuiltin__Init 之后,Python会把PyModuleObject对象中维护的那个PyDictObject对象抽取出来,将其赋值给 interp -> builtins 。

//moduleobject.c

PyObject *

PyModule_GetDict(PyObject *m)

{

PyObject *d;

if (!PyModule_Check(m)) {

PyErr_BadInternalCall();

return NULL;

}

d = ((PyModuleObject *)m) -> md_dict;

assert(d != NULL);

return d;

}

static PyStatus

new_interpreter(PyThreadState **tstate_p)

{

//......

PyObject *bimod = _PyImport_FindBuiltin("builtins", modules);

if (bimod != NULL) {

//通过PyModule_GetDict获取属性字典, 赋值给builtins

interp->builtins = PyModule_GetDict(bimod);

if (interp->builtins == NULL)

goto handle_error;

Py_INCREF(interp->builtins);

}

else if (PyErr_Occurred()) {

goto handle_error;

}

//......

}

以后Python在需要访问__builtins__时,直接访问 interp->builtins 就可以了,不需要再到 interp->modules 里面去找了。因为对于内置函数、属性的使用在Python中会比较频繁,所以这种加速机制是很有效的。

创建sys module

Python在创建并设置了__builtins__之后,会照猫画虎,用同样的流程来设置sys module,并像设置 interp->builtins 一样设置 interp->sysdict 。

//Python/pylifecycle.c

static PyStatus

new_interpreter(PyThreadState **tstate_p)

{

//.......

PyObject *sysmod = _PyImport_FindBuiltin("sys", modules);

if (sysmod != NULL) {

interp->sysdict = PyModule_GetDict(sysmod);

if (interp->sysdict == NULL) {

goto handle_error;

}

Py_INCREF(interp->sysdict);

//设置

PyDict_SetItemString(interp->sysdict, "modules", modules);

if (_PySys_InitMain(runtime, interp) < 0) {

return _PyStatus_ERR("can't finish initializing sys");

}

}

//.......

}

Python在创建了sys module之后,会在此module中设置一个Python搜索module时的默认路径集合。

//Python/pylifecycle.c

static PyStatus

new_interpreter(PyThreadState **tstate_p)

{

//.......

status = add_main_module(interp);

//.......

}

static PyStatus

add_main_module(PyInterpreterState *interp)

{

PyObject *m, *d, *loader, *ann_dict;

//将__main__添加进sys.modules中

m = PyImport_AddModule("__main__");

if (m == NULL)

return _PyStatus_ERR("can't create __main__ module");

d = PyModule_GetDict(m);

ann_dict = PyDict_New();

if ((ann_dict == NULL) ||

(PyDict_SetItemString(d, "__annotations__", ann_dict) < 0)) {

return _PyStatus_ERR("Failed to initialize __main__.__annotations__");

}

Py_DECREF(ann_dict);

if (PyDict_GetItemString(d, "__builtins__") == NULL) {

PyObject *bimod = PyImport_ImportModule("builtins");

if (bimod == NULL) {

return _PyStatus_ERR("Failed to retrieve builtins module");

}

if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {

return _PyStatus_ERR("Failed to initialize __main__.__builtins__");

}

Py_DECREF(bimod);

}

loader = PyDict_GetItemString(d, "__loader__");

if (loader == NULL || loader == Py_None) {

PyObject *loader = PyObject_GetAttrString(interp->importlib,

"BuiltinImporter");

if (loader == NULL) {

return _PyStatus_ERR("Failed to retrieve BuiltinImporter");

}

if (PyDict_SetItemString(d, "__loader__", loader) < 0) {

return _PyStatus_ERR("Failed to initialize __main__.__loader__");

}

Py_DECREF(loader);

}

return _PyStatus_OK();

}

根据我们使用Python的经验,我们知道最终Python肯定会创建一个PyListObject对象,也就是Python中的sys.path,里面包含了一组PyUnicodeObject,每一个PyUnicodeObject的内容就代表了一个搜索路径。但是这一步不是在这里完成的,至于是在哪里完成的,我们后面会说。

另外,我们需要注意的是:在上面的逻辑中,解释器将__main__这个模块添加进去了,这个__main__估计不用我多说了。之前在 PyModule_New 中,创建一个PyModuleObject对象之后,会在其属性字典(md_dict获取)中插入一个名为"__name__"的key,value就是 "__main__"。但是对于当然模块来说,这个模块也可以叫做__main__。

name = "神楽七奈"

import __main__

print(__main__.name) # 神楽七奈

import sys

print(sys.modules["__main__"] is __main__) # True

我们发现这样也是可以导入的,因为这个__main__就是这个模块本身。

static PyStatus

add_main_module(PyInterpreterState *interp)

{

PyObject *m, *d, *loader, *ann_dict;

//创建__main__ module,并将其插入到interp->modules中

m = PyImport_AddModule("__main__");

if (m == NULL)

return _PyStatus_ERR("can't create __main__ module");

//获取__main__的属性字典

d = PyModule_GetDict(m);

//获取interp->modules中的__builtins__ module

if (PyDict_GetItemString(d, "__builtins__") == NULL) {

PyObject *bimod = PyImport_ImportModule("builtins");

if (bimod == NULL) {

return _PyStatus_ERR("Failed to retrieve builtins module");

}

//将("__builtins__", __builtins__)插入到__main__ module的dict中

if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {

return _PyStatus_ERR("Failed to initialize __main__.__builtins__");

}

Py_DECREF(bimod);

}

//......

}

因此我们算是知道了,为什么python xxx.py执行的时候,__name__是__main__了,因为我们这里设置了。而Python沿着名字空间寻找的时候,最终会在__main__的local空间中发现__name__,且值为字符串"__main__"。但如果是以import的方式加载的,那么__name__则不是"__main__",而是模块名,后面我们会继续说。

1229382-20191226190436915-1589229349.png

其实这个__main__我们是再熟悉不过的了,当输入dir()的时候,就会显示__main__的内容。dir是可以不加参数的,如果不加参数,那么默认访问当前的py文件,也就是__main__。

>>> __name__

'__main__'

>>>

>>> __builtins__.__name__

'builtins'

>>>

>>> import numpy as np

>>> np.__name__

'numpy'

>>>

所以说,访问模块就类似访问变量一样。modules里面存放了所有的(module name, PyModuleObject),当我们调用np的时候,是会找到name为"numpy"的值,然后这个值里面也维护了一个字典,其中就有一个key为__name__的entry。

设置site-specific的module的搜索路径

Python是一个非常开放的体系,它的强大来源于丰富的第三方库,这些库由外部的py文件来提供,当使用这些第三方库的时候,只需要简单的进行import即可。一般来说,这些第三方库都放在/lib/site-packages中,如果程序想使用这些库,直接把库放在这里面即可。

但是到目前为止,我们好像也没看到python将site-packages路径设置到搜索路径里面去啊。其实在完成了__main__的创建之后,Python才腾出手来,收拾这个site-package。这个关键的动作在于Python的一个标准库:site.py。

我们先来将Lib目录下的site.py删掉,然后导入一个第三方模块,看看会有什么后果。

1229382-20200909213801726-2007195328.png

因此我们发现,Python在初始化的过程中确实导入了site.py,所以才有了如下的输出。而这个site.py也正是Python能正确加载位于site-packages目录下第三方包的关键所在。我们可以猜测,应该就是这个site.py将site-packages目录加入到了前面的sys.path中,而这个动作是由 init_import_size 完成的。

static PyStatus

new_interpreter(PyThreadState **tstate_p)

{

//......

if (config->site_import) {

status = init_import_size();

if (_PyStatus_EXCEPTION(status)) {

return status;

}

}

//......

}

static PyStatus

init_import_size(void)

{

PyObject *m;

m = PyImport_ImportModule("site");

if (m == NULL) {

//这里的报错信息是不是和上图中显示的一样呢?

return _PyStatus_ERR("Failed to import the site module");

}

Py_DECREF(m);

return _PyStatus_OK();

}

在 init_import_size 中,只调用了 PyImport_ImportModule 函数,这个函数是Python中import机制的核心所在。PyImport_ImportModule("numpy")等价于python中的 import numpy 即可。

激活python虚拟机

Python运行方式有两种,一种是在命令行中运行的交互式环境;另一种则是以python xxx.py方式运行脚本文件。尽管方式不同,但是却殊途同归,进入同一个字节码虚拟机。

Python在 Py_Initialize 完成之后,最终会通过 pymain_run_file 调用 PyRun_AnyFileExFlags。

//Modules/main.c

static int

pymain_run_file(PyConfig *config, PyCompilerFlags *cf)

{

//那么获取文件名

const wchar_t *filename = config->run_filename;

if (PySys_Audit("cpython.run_file", "u", filename) < 0) {

return pymain_exit_err_print();

}

//打开文件

FILE *fp = _Py_wfopen(filename, L"rb");

//如果fp为NULL, 证明文件打开失败

if (fp == NULL) {

char *cfilename_buffer;

const char *cfilename;

int err = errno;

cfilename_buffer = _Py_EncodeLocaleRaw(filename, NULL);

if (cfilename_buffer != NULL)

cfilename = cfilename_buffer;

else

cfilename = "";

fprintf(stderr, "%ls: can't open file '%s': [Errno %d] %s\n",

config->program_name, cfilename, err, strerror(err));

PyMem_RawFree(cfilename_buffer);

return 2;

}

//......

//调用PyRun_AnyFileExFlags

int run = PyRun_AnyFileExFlags(fp, filename_str, 1, cf);

Py_XDECREF(bytes);

return (run != 0);

}

//Python/pythonrun.c

int

PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,

PyCompilerFlags *flags)

{

if (filename == NULL)

filename = "???";

//根据fp是否代表交互环境,对程序进行流程控制

if (Py_FdIsInteractive(fp, filename)) {

//如果是交互环境,那么调用PyRun_InteractiveLoopFlags

int err = PyRun_InteractiveLoopFlags(fp, filename, flags);

if (closeit)

fclose(fp);

return err;

}

else

//否则说明是一个普通的python脚本,执行PyRun_SimpleFileExFlags

return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);

}

我们看到交互式和py脚本式走的两条不同的路径,但是别着急,最终你会看到它们又会分久必合、走向同一条路径。

交互式运行

先来看看交互式运行时候的情形,不过在此之前先来看一下提示符。

>>> a = 1

>>> if a == 1:

... pass

...

>>>

>>> import sys

>>> sys.ps1 = "matsuri:"

matsuri:a = 1

matsuri:a

1

matsuri:

matsuri:sys.ps2 = "fubuki:"

matsuri:if a == 1:

fubuki: pass

fubuki:

matsuri:

我们每输入一行,开头都是>>>,这个是sys.ps1,而输入语句块的时候,没输入完的时候,那么显示...,这个是sys.ps2。如果修改了,那么就是我们自己定义的了。

int

PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)

{

//....

//创建交互式提示符

v = _PySys_GetObjectId(&PyId_ps1);

if (v == NULL) {

_PySys_SetObjectId(&PyId_ps1, v = PyUnicode_FromString(">>> "));

Py_XDECREF(v);

}

//同理这个也是一样

v = _PySys_GetObjectId(&PyId_ps2);

if (v == NULL) {

_PySys_SetObjectId(&PyId_ps2, v = PyUnicode_FromString("... "));

Py_XDECREF(v);

}

err = 0;

do {

//这里就进入了交互式环境,我们看到每次都调用了PyRun_InteractiveOneObjectEx

//直到下面的ret != E_EOF不成立 停止循环,一般情况就是我们输入exit()图此处了

ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);

if (ret == -1 && PyErr_Occurred()) {

if (PyErr_ExceptionMatches(PyExc_MemoryError)) {

if (++nomem_count > 16) {

PyErr_Clear();

err = -1;

break;

}

} else {

nomem_count = 0;

}

PyErr_Print();

flush_io();

} else {

nomem_count = 0;

}

//......

} while (ret != E_EOF);

Py_DECREF(filename);

return err;

}

static int

PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,

PyCompilerFlags *flags)

{

PyObject *m, *d, *v, *w, *oenc = NULL, *mod_name;

mod_ty mod;

PyArena *arena;

const char *ps1 = "", *ps2 = "", *enc = NULL;

int errcode = 0;

_Py_IDENTIFIER(encoding);

_Py_IDENTIFIER(__main__);

mod_name = _PyUnicode_FromId(&PyId___main__); /* borrowed */

if (mod_name == NULL) {

return -1;

}

if (fp == stdin) {

//......

}

v = _PySys_GetObjectId(&PyId_ps1);

if (v != NULL) {

//......

}

w = _PySys_GetObjectId(&PyId_ps2);

if (w != NULL) {

//.....

}

//编译用户在交互式环境下输入的python语句

arena = PyArena_New();

if (arena == NULL) {

Py_XDECREF(v);

Py_XDECREF(w);

Py_XDECREF(oenc);

return -1;

}

//生成抽象语法树

mod = PyParser_ASTFromFileObject(fp, filename, enc,

Py_single_input, ps1, ps2,

flags, &errcode, arena);

Py_XDECREF(v);

Py_XDECREF(w);

Py_XDECREF(oenc);

if (mod == NULL) {

PyArena_Free(arena);

if (errcode == E_EOF) {

PyErr_Clear();

return E_EOF;

}

return -1;

}

//获取中维护的dict

m = PyImport_AddModuleObject(mod_name);

if (m == NULL) {

PyArena_Free(arena);

return -1;

}

d = PyModule_GetDict(m);

//执行用户输入的python语句

v = run_mod(mod, filename, d, d, flags, arena);

PyArena_Free(arena);

if (v == NULL) {

return -1;

}

Py_DECREF(v);

flush_io();

return 0;

}

我们发现在run_mod之前,python会将__main__中维护的PyDictObject对象取出,作为参数传递给run_mod,这个参数关系极为重要,实际上这里的参数d就将作为Python虚拟机开始执行时当前活动的frame对象的local名字空间和global名字空间。

脚本文件运行方式

接下来,我们看一看直接运行脚本文件的方式。

//.include/compile.h

#define Py_file_input 257

//Python/pythonrun.c

int

PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,

PyCompilerFlags *flags)

{

PyObject *m, *d, *v;

const char *ext;

int set_file_name = 0, ret = -1;

size_t len;

//__main__就是当前文件

m = PyImport_AddModule("__main__");

if (m == NULL)

return -1;

Py_INCREF(m);

//还记得这个d吗?当前活动的frame对象的local和global名字空间

d = PyModule_GetDict(m);

//在__main__中设置__file__属性

if (PyDict_GetItemString(d, "__file__") == NULL) {

PyObject *f;

f = PyUnicode_DecodeFSDefault(filename);

if (f == NULL)

goto done;

if (PyDict_SetItemString(d, "__file__", f) < 0) {

Py_DECREF(f);

goto done;

}

if (PyDict_SetItemString(d, "__cached__", Py_None) < 0) {

Py_DECREF(f);

goto done;

}

set_file_name = 1;

Py_DECREF(f);

}

len = strlen(filename);

ext = filename + len - (len > 4 ? 4 : 0);

//如果是pyc

if (maybe_pyc_file(fp, filename, ext, closeit)) {

FILE *pyc_fp;

//二进制模式打开

if (closeit)

fclose(fp);

if ((pyc_fp = _Py_fopen(filename, "rb")) == NULL) {

fprintf(stderr, "python: Can't reopen .pyc file\n");

goto done;

}

if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) {

fprintf(stderr, "python: failed to set __main__.__loader__\n");

ret = -1;

fclose(pyc_fp);

goto done;

}

v = run_pyc_file(pyc_fp, filename, d, d, flags);

} else {

if (strcmp(filename, "") != 0 &&

set_main_loader(d, filename, "SourceFileLoader") < 0) {

fprintf(stderr, "python: failed to set __main__.__loader__\n");

ret = -1;

goto done;

}

//执行脚本文件

v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,

closeit, flags);

}

//.......

}

PyObject *

PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,

PyObject *locals, int closeit, PyCompilerFlags *flags)

{

PyObject *ret = NULL;

//......

//编译

mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,

flags, NULL, arena);

if (closeit)

fclose(fp);

if (mod == NULL) {

goto exit;

}

//执行, 依旧是调用了runmod

ret = run_mod(mod, filename, globals, locals, flags, arena);

exit:

Py_XDECREF(filename);

if (arena != NULL)

PyArena_Free(arena);

return ret;

}

很显然,脚本文件和交互式之间的执行流程是不同的,但是最终都进入了run_mod,而且同样也将与__main__中维护的PyDictObject对象作为local名字空间和global名字空间传入了run_mod。

启动虚拟机

是的你没有看错,下面才是启动虚拟机,之前做了那么工作都是前戏。

static PyObject *

run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,

PyCompilerFlags *flags, PyArena *arena)

{

PyCodeObject *co;

PyObject *v;

//基于ast编译字节码指令序列,创建PyCodeObject对象

co = PyAST_CompileObject(mod, filename, flags, -1, arena);

if (co == NULL)

return NULL;

if (PySys_Audit("exec", "O", co) < 0) {

Py_DECREF(co);

return NULL;

}

//创建PyFrameObject,执行PyCodeObject对象中的字节码指令序列

v = run_eval_code_obj(co, globals, locals);

Py_DECREF(co);

return v;

}

run_mod接手传来的ast,然后传到 PyAST_CompileObject 中,创建了一个我们已经非常熟悉的PyCodeObject对象。关于这个完整的编译过程,就又是另一个话题了,总之先是scanner进行词法分析、将源代码切分成一个个的token,然后parser在词法分析之后的结果之上进行语法分析、通过切分好的token生成抽象语法树(AST,abstract syntax tree),然后将AST编译PyCodeObject对象,最后再由虚拟机执行。知道这么一个大致的流程即可,至于到底是怎么分词、怎么建立语法树的,这就又是一个难点了,个人觉得甚至比研究Python虚拟机还难。有兴趣的话可以去看Python源码中Parser目录,如果能把Python的分词、语法树的建立给了解清楚,那我觉得你完全可以手写一个正则表达式的引擎、以及各种模板语言。

而接下来,Python已经做好一切工作,开始通过 run_eval_code_obj 着手唤醒字节码虚拟机。

static PyObject *

run_eval_code_obj(PyCodeObject *co, PyObject *globals, PyObject *locals)

{

PyObject *v;

//......

v = PyEval_EvalCode((PyObject*)co, globals, locals);

if (!v && PyErr_Occurred() == PyExc_KeyboardInterrupt) {

_Py_UnhandledKeyboardInterrupt = 1;

}

return v;

}

函数中调用了 PyEval_EvalCode,根据前面介绍函数的时候,我们知道最终一定会走到 PyEval_EvalFrameEx。

从操作系统创建进程,进程创建线程,线程设置builtins(包括设置__name__、内建对象、内置函数方法等等)、设置缓存池,然后各种初始化,设置搜索路径。最后分词、编译、激活虚拟机执行。而执行的这个过程就是曾经与我们朝夕相处的 PyEval_EvalFrameEx ,掌控Python世界中无数对象的生生灭灭。参数f就是PyFrameObject对象,我们曾经探索了很久,现在一下子就回到了当初。有种梦回栈帧对象的感觉。目前的话,Python的骨架我们已经看清了,虽然还有很多细节隐藏在幕后。至少神秘的面纱已经被撤掉了。

名字空间

现在我们来看一下有趣的东西,看看在激活字节码虚拟机、创建 PyFrameObject 对象时,所设置的3个名字空间:local、global、builtin。

//Objects/frameobject.c

PyFrameObject*

PyFrame_New(PyThreadState *tstate, PyCodeObject *code,

PyObject *globals, PyObject *locals)

{

PyFrameObject *f = _PyFrame_New_NoTrack(tstate, code, globals, locals);

if (f)

_PyObject_GC_TRACK(f);

return f;

}

PyFrameObject* _Py_HOT_FUNCTION

_PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,

PyObject *globals, PyObject *locals)

{

PyFrameObject *back = tstate->frame;

PyFrameObject *f;

PyObject *builtins;

Py_ssize_t i;

//设置builtin名字空间

if (back == NULL || back->f_globals != globals) {

//但是我们发现设置builtins,居然是从globals里面获取的

//带着这个疑问,看看下面更大的疑问

builtins = _PyDict_GetItemIdWithError(globals, &PyId___builtins__);

//......

}

else {

/* If we share the globals, we share the builtins.

Save a lookup and a call. */

builtins = back->f_builtins;

assert(builtins != NULL);

Py_INCREF(builtins);

}

//.......

//设置builtins

f->f_builtins = builtins;

//....

//设置globals

f->f_globals = globals;

if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) ==

(CO_NEWLOCALS | CO_OPTIMIZED))

; /* f_locals = NULL; will be set by PyFrame_FastToLocals() */

else if (code->co_flags & CO_NEWLOCALS) {

locals = PyDict_New();

if (locals == NULL) {

Py_DECREF(f);

return NULL;

}

f->f_locals = locals;

}

else {

if (locals == NULL)

//如果locals为NULL,那么等同于globals,显然这是针对模块来的

locals = globals;

Py_INCREF(locals);

f->f_locals = locals;

}

f->f_lasti = -1;

f->f_lineno = code->co_firstlineno;

f->f_iblock = 0;

f->f_executing = 0;

f->f_gen = NULL;

f->f_trace_opcodes = 0;

f->f_trace_lines = 1;

return f;

}

我们说内置名字空间是从global名字空间里面获取的,我们用Python来演示一下。

# 代表了globals()里面存放了builtins

print(globals()["__builtins__"]) #

# 我们说builtins里面包含了所有的内置对象、函数等等,显然调用int是可以的

print(globals()["__builtins__"].int("123")) # 123

# 但是,我居然能从builtins里面拿到globals

# 不过也很好理解,因为globals是一个内置函数,肯定是在builtins里面

print(globals()["__builtins__"].globals) #

# 于是拿到了globals,继续调用,然后获取__builtins__,又拿到了builtins,而且我们是可以调用list的

print(globals()["__builtins__"].globals()["__builtins__"].list("abcd")) # ['a', 'b', 'c', 'd']

所以不管套娃多少次,都是可以的,因为它们都是指针。

可以看到builtin和global空间里面都存储一个能够获取对方空间的一个函数指针, 所以这两者是并不冲突的。当然除此之外,还有一个__name__,注意我们之前说设置__name__只是builtins的__name__,并不是当前模块的。

# 我们看到,builtins里面获取的__name__居然不是__main__,而是builtins

print(globals()["__builtins__"].__name__) # builtins

# 首先按照local global builtin的顺序查找是没问题的

# 而对于模块来说,我们知道local空间为NULL的话,然后直接把global空间交给local空间了

# 而local里面有__name__,就是__main__,所以__name__和builtins.__name__不是一个东西

print(globals()["__name__"]) # __main__

# 初始化builtins的时候,那个__name__指的是builtins这个PyModuleObject的__name__

# 而对于我们py文件这个模块来说,__name__是设置在global名字空间里面的

# 如果将global空间或者local空间里面的__name__删掉,那么按照顺序就会寻找builtin里面的__name__,此时就会打印builtins了。

globals().pop("__name__")

print(__name__) # builtins

1229382-20200909213825050-2076588242.png

所以我们看到__name__这个属性是在启动之后动态设置的,如果执行的文件和该文件是同一个文件,那么__name__就会是__main__;如果不是同一个文件,证明这个文件是作为模块被导入进来的,那么此时它的__name__就是文件名。

更多细节可以前往源码中查看,Python运行环境初始化还是比较复杂的。

小结

这一次我们说了Python运行环境的初始化,或者说当Python启动的时候都做了哪些事情。可以看到,做的事情不是一般的多,真的准备了大量的工作。因为Python是动态语言,这就意味很多操作都要发生在运行时。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值