14.0 序
我们之前分析了python的核心--字节码、虚拟机的剖析工作。当时我们说过,这仅仅只是一部分,还有一部分内容被遮在了幕后。而这一章,我们将回到时间的起点,从python的应用程序被执行开始,一步一步紧紧跟随python的踪迹,完整地展示python在启动之初的所有动作。当我们跟随python完成所有的初始化动作之后,也就能对python执行引擎执行字节码指令时的整个运行环境了如指掌了。
14.1 线程环境初始化
14.1.1 线程模型回顾
我们之前介绍栈帧的时候,说过python中的线程模型,python中的一个线程对应C中的一个线程,也对应操作系统中的一个原生线程。当时还强调,python中的线程和操作系统线程是对应的。但是每个线程都对应一个PyThreadState对象,记录了真正的执行线程的状态。而python在启动之后,初始化的动作是从_Py_InitializeCore
开始的,然后这个函数调用了_Py_InitializeCore_impl
进行完成初始化,我们分析会从_Py_InitializeCore_impl
开始,当然_Py_InitializeCore
里面也做了一些工作,具体的我们后面会介绍。下面先看看进程和线程在cpython中的定义。
//pystate.h
typedef struct _is PyInterpreterState;
typedef struct _is {
struct _is *next;
struct _ts *tstate_head; //模拟进程环境中的线程集合
int64_t id;
int64_t id_refcount;
PyThread_type_lock id_mutex;
PyObject *modules;
PyObject *modules_by_index;
PyObject *sysdict;
PyObject *builtins;
PyObject *importlib;
int check_interval;
long num_threads;
size_t pythread_stacksize;
PyObject *codec_search_path;
PyObject *codec_search_cache;
PyObject *codec_error_registry;
int codecs_initialized;
int fscodec_initialized;
_PyCoreConfig core_config;
_PyMainInterpreterConfig config;
#ifdef HAVE_DLOPEN
int dlopenflags;
#endif
PyObject *builtins_copy;
PyObject *import_func;
_PyFrameEvalFunction eval_frame;
Py_ssize_t co_extra_user_count;
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
#ifdef HAVE_FORK
PyObject *before_forkers;
PyObject *after_forkers_parent;
PyObject *after_forkers_child;
#endif
void (*pyexitfunc)(PyObject *);
PyObject *pyexitmodule;
uint64_t tstate_next_unique_id;
} PyInterpreterState;
#endif
typedef struct _ts PyThreadState;
typedef struct _ts {
struct _ts *prev;
struct _ts *next;
PyInterpreterState *interp;
struct _frame *frame; //栈帧对象,模拟线程中函数的调用堆栈
int recursion_depth;
char overflowed;
char recursion_critical;
int stackcheck_counter;
int tracing;
int use_tracing;
Py_tracefunc c_profilefunc;
Py_tracefunc c_tracefunc;
PyObject *c_profileobj;
PyObject *c_traceobj;
PyObject *curexc_type;
PyObject *curexc_value;
PyObject *curexc_traceback;
_PyErr_StackItem exc_state;
_PyErr_StackItem *exc_info;
PyObject *dict;
int gilstate_counter;
PyObject *async_exc;
unsigned long thread_id;
int trash_delete_nesting;
PyObject *trash_delete_later;
void (*on_delete)(void *);
void *on_delete_data;
int coroutine_origin_tracking_depth;
PyObject *coroutine_wrapper;
int in_coroutine_wrapper;
PyObject *async_gen_firstiter;
PyObject *async_gen_finalizer;
PyObject *context;
uint64_t context_ver;
/* Unique thread state id. */
uint64_t id;
/* XXX signal handlers should also be here */
} PyThreadState;
#endif /* !Py_LIMITED_API */
其中PyInterpreterState
是对进程的模拟,而PyThreadState
则是对线程的模拟,下面还是我们之前画的,python虚拟机运行期间某个时刻整个的运行环境。
14.1.2 初始化线程环境
在Windows平台上,当执行一个可执行文件时,操作系统首先会创建一个进程内核对象。同理在python中也是如此,会在_Py_InitializeCore_impl
中调用PyInterpreterState_New
创建一个崭新的PyInterpreterState
对象。
//pystate.c
PyInterpreterState *
PyInterpreterState_New(void)
{
PyInterpreterState *interp = (PyInterpreterState *)
PyMem_RawMalloc(sizeof(PyInterpreterState));
if (interp == NULL) {
return NULL;
}
interp->id_refcount = -1;
interp->id_mutex = NULL;
interp->modules = NULL;
interp->modules_by_index = NULL;
interp->sysdict = NULL;
interp->builtins = NULL;
interp->builtins_copy = NULL;
interp->tstate_head = NULL;
interp->check_interval = 100;
interp->num_threads = 0;
interp->pythread_stacksize = 0;
interp->codec_search_path = NULL;
interp->codec_search_cache = NULL;
interp->codec_error_registry = NULL;
interp->codecs_initialized = 0;
interp->fscodec_initialized = 0;
interp->core_config = _PyCoreConfig_INIT;
interp->config = _PyMainInterpreterConfig_INIT;
interp->importlib = NULL;
interp->import_func = NULL;
interp->eval_frame = _PyEval_EvalFrameDefault;
interp->co_extra_user_count = 0;
#ifdef HAVE_DLOPEN
#if HAVE_DECL_RTLD_NOW
interp->dlopenflags = RTLD_NOW;
#else
interp->dlopenflags = RTLD_LAZY;
#endif
#endif
#ifdef HAVE_FORK
interp->before_forkers = NULL;
interp->after_forkers_parent = NULL;
interp->after_forkers_child = NULL;
#endif
interp->pyexitfunc = NULL;
interp->pyexitmodule = NULL;
HEAD_LOCK();
if (_PyRuntime.interpreters.next_id < 0) {
/* overflow or Py_Initialize() not called! */
PyErr_SetString(PyExc_RuntimeError,
"failed to get an interpreter ID");
PyMem_RawFree(interp);
interp = NULL;
} else {
interp->id = _PyRuntime.interpreters.next_id;
_PyRuntime.interpreters.next_id += 1;
interp->next = _PyRuntime.interpreters.head;
if (_PyRuntime.interpreters.main == NULL) {
_PyRuntime.interpreters.main = interp;
}
_PyRuntime.interpreters.head = interp;
}
HEAD_UNLOCK();
if (interp == NULL) {
return NULL;
}
interp->tstate_next_unique_id = 0;
return interp;
}
关于进程方面我们不做过多解释,只需要知道python在运行的时候,会创建一个、或者多个PyInterpreterState(进程)
对象,然后通过next指针将多个PyInterpreterState
形成一个链表结构
在PyInterpreterState_New
成功创建PyInterpreterState
之后,会再接再厉,调用PyThreadState_New
创建一个全新的PyThreadState(线程)
对象。
//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)
{
PyThreadState *tstate = (PyThreadState *)PyMem_RawMalloc(sizeof(PyThreadState));
//设置从线程中获取函数调用栈的操作
if (_PyThreadState_GetFrame == NULL)
_PyThreadState_GetFrame = threadstate_getframe;
if (tstate != NULL) {
//在PyThreadState对象中关联PyInterpreterState对象
tstate->interp = interp;
//下面设置线程的属性,我们说了python中的线程是对操作系统线程的模拟
//抽象成一些属性
tstate->frame = NULL;
tstate->recursion_depth = 0;
tstate->overflowed = 0;
tstate->recursion_critical = 0;
tstate->stackcheck_counter = 0;
tstate->tracing = 0;
tstate->use_tracing = 0;
tstate->gilstate_counter = 0;
tstate->async_exc = NULL;
tstate->thread_id = PyThread_get_thread_ident();
tstate->dict = NULL;
tstate->curexc_type = NULL;
tstate->curexc_value = NULL;
tstate->curexc_traceback = NULL;
tstate->exc_state.exc_type = NULL;
tstate->exc_state.exc_value = NULL;
tstate->exc_state.exc_traceback = NULL;
tstate->exc_state.previous_item = NULL;
tstate->exc_info = &tstate->exc_state;
tstate->c_profilefunc = NULL;
tstate->c_tracefunc = NULL;
tstate->c_profileobj = NULL;
tstate->c_traceobj = NULL;
tstate->trash_delete_nesting = 0;
tstate->trash_delete_later = NULL;
tstate->on_delete = NULL;
tstate->on_delete_data = NULL;
tstate->coroutine_origin_tracking_depth = 0;
tstate->coroutine_wrapper = NULL;
tstate->in_coroutine_wrapper = 0;
tstate->async_gen_firstiter = NULL;
tstate->async_gen_finalizer = NULL;
tstate->context = NULL;
tstate->context_ver = 1;
tstate->id = ++interp->tstate_next_unique_id;
if (init)
_PyThreadState_Init(tstate);
HEAD_LOCK();
tstate->prev = NULL;
tstate->next = interp->tstate_head;
if (tstate->next)
tstate->next->prev = tstate;
//在PyInterpreterState对象关联PyThreadState对象
interp->tstate_head = tstate;
HEAD_UNLOCK();
}
return tstate;
}
与PyInterpreterState_New
相同,PyThreadState_New
申请内存,创建PyThreadState对象,并且对其中各个域进行初始化。但是我们注意到,在PyThreadState
结构体中,也存在着一个next指针,肯定会在python运行的某个时刻,如上面图中显示的那样,存在多个PyThreadState
对象形成一个链表。显然用鼻子像也知道这是和python的多线程实现有关。
我们说python设置了从线程中获取函数调用栈的操作,所谓函数调用栈就是我们前面章节说的PyFrameObject对象链表。而且在源码中,我们看到了PyThreadState
关联了PyInterpreterState
,PyInterpreterState
也关联了PyInterpreterState
,到目前为止,仅有的两个对象建立起了联系。对应到Windows,或者说操作系统,我们说进程和线程建立了联系
在PyInterpreterState
和PyThreadState
建立了联系之后,那么就很容易在PyInterpreterState
和PyThreadState
之间穿梭。并且在python运行时环境中,会有一个变量_PyThreadState_Current
,一直维护着当前活动的线程,更准确的说是当前活动线程对应的PyThreadState
对象。初始时,该变量为NULL。在创建了python启动之后的第一个PyThreadState
之后,会用该PyThreadState
对象调用PyThreadState_Swap
函数来设置这个变量
//pystate.c
PyThreadState *
PyThreadState_Swap(PyThreadState *newts)
{
PyThreadState *oldts = GET_TSTATE();
SET_TSTATE(newts);
return oldts;
}
//pystate.h
#define _PyThreadState_Current _PyRuntime.gilstate.tstate_current
//pystate.c
/*
GET_TSTATE是一个带参数的宏,调用了_Py_atomic_store_relaxed
*/
#define GET_TSTATE()
((PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current))
#define SET_TSTATE(value)
_Py_atomic_store_relaxed(&_PyThreadState_Current, (uintptr_t)(value))
//pyatomic.h
#define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL)
_Py_atomic_store_explicit(ATOMIC_VAL, NEW_VAL, _Py_memory_order_relaxed)
#define _Py_atomic_store_explicit(ATOMIC_VAL, NEW_VAL, ORDER)
atomic_store_explicit(&(ATOMIC_VAL)->_value, NEW_VAL, ORDER)
//而atomic_store_explicit是系统头文件stdatomic.h中定义的api,这是在系统的api中修改的,所以说是线程安全的
接着,python初始化动作开始转向python类型的初始化,这个转折是在调用_Py_InitializeCore_impl
中调用_Py_ReadyTypes
时开始的。
//Python/pylifecycle.c
_PyInitError
_Py_InitializeCore_impl(PyInterpreterState **interp_p,
const _PyCoreConfig *core_config)
{
PyInterpreterState *interp;
_PyInitError err;
...
...
//创建进程
interp = PyInterpreterState_New();
...
...
//创建线程
PyThreadState *tstate = PyThreadState_New(interp);
//类型系统初始化
_Py_ReadyTypes();
if (!_PyFrame_Init())
return _Py_INIT_ERR("can't init frames");
if (!_PyLong_Init())
return _Py_INIT_ERR("can't init longs");
if (!PyByteArray_Init())
return _Py_INIT_ERR("can't init bytearray");
if (!_PyFloat_Init())
return _Py_INIT_ERR("can't init float");
...
...
}
类型系统的初始化是一套相当反复的动作,在介绍python的类机制时,我们已经介绍过了。紧接着,会调用各种XXX_Init()
进行初始化,比如在PyLong_Init
中会初始化我们在剖析python整数对象时看到的那个庞大的整数对象系统等等,这里就不再细看了,有兴趣的可以阅读源码。
到这里,我们对_Py_InitializeCore_impl
算是有了一个阶段性的成功,我们创建了代表进程和线程概念的PyInterpreterState
和PyThreadState
对象,并且在它们之间建立的联系。下面,_Py_InitializeCore_impl
将进行入另一个环节,设置系统module。
14.2 系统module初始化
当我们想查看一个object支持哪些方法的时候,会使用内置函数dir(obj),然后显示一个list的内容,我们先不管它是如何运作、以及输出的信息是什么,我们单来看看这个调用本身就很有意思。我们知道python如果执行dir(),那么必定是在某个命名空间中找到了符号dir所对应的对象,而这还是一个callable的对象。不过我们先来考虑符号dir的存在性,根据使用python的经验,这个dir显然是可以直接使用的,这就代表了python启动之后就已经创建了一个命名空间,这个空间里面存在着符号dir。而这个命名空间就来自于系统module,这些module正是在_Py_InitializeCore_impl
中设置的。其中第一个被python创建的module就是__builtins__
。
14.2.1 创建_builtins_
在_Py_InitializeCore_impl
,当python创建了PyInterpreterState
和PyThreadState
对象之后,就会开始通过_PyBuiltin_Init
来设置系统的__builtins__
了。
//Python/pylifecycle.c
_PyInitError
_Py_InitializeCore_impl(PyInterpreterState **interp_p,
const _PyCoreConfig *core_config)
{
PyInterpreterState *interp;
_PyInitError err;
//申请创建一个PyDictObject
PyObject *modules = PyDict_New();
if (modules == NULL)
return _Py_INIT_ERR("can't make modules dictionary");
interp->modules = modules;
PyObject *bimod = _PyBuiltin_Init();
}
在调用_PyBuiltin_Init
之前,我们看到了interp->modules
指向了modules,就是说这个域将维护系统所有的modules。而interp是一个PyInterpreterState
对象,因为我们知道操作系统的进程维护这线程使用的资源,而且这些资源是共享的,所以这是很好理解的,不可能说一个线程维护这一个modules,否则就意味着每创建一个线程就要创建一份__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]
"""
不管是dir、hasattr等函数,list、int、dict等内建对象都是在__builtins__
里面,我们通过builtins模块可以直接获取,这算是python给我们提供的一个接口。而我们在foo1中把list和tuple互相对调了,而这么做显然影响到了foo2函数。而foo2是后执行的,但是被影响了,也说明了__builtins__
这个内置的命名空间是属于进程级别的,多个线程是共享的。另外这个interp->modules
在python一级指的就是sys.modules
//Python/builtinmodule.c
PyObject *
_PyBuiltin_Init(void)
{
PyObject *mod, *dict, *debug;
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(Py_OptimizeFlag == 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,而这个过程是分为两步的。
创建PyModuleObject对象,在python中,module正是通过这个对象来实现的,我们后续章节介绍模块的时候会重点说。
设置module,将python中所有的内建对象都塞到__builtins__中。
但是我们看到设置的东西似乎少了不少,比如刚才说的dir、hasattr、setattr等等,这些明显也是内置的,但是它们到哪里去了。别急,我们刚才说创建__builtins__
分为两步,第一步是创建PyModuleObject
,而使用的函数就是_PyModule_CreateInitialized
,而在这个函数里面就已经完成了大部分设置__builtins__
的工作
PyObject *
_PyModule_CreateInitialized(struct PyModuleDef* module, int module_api_version)
{
const char* name;
PyModuleObject *m;
...
//拿到module的name,对于当前来说,这里显然是__builtins__
name = module->m_name;
//这里比较有意思,这是检测模块版本的,针对是需要导入的py文件。
//我们说编译成字节码之后,会直接从当前目录的__pycache__里面导入
//而那里面都是pyc文件,介绍字节码的时候我们说,pyc文件的文件名是有python解释器的版本号的
//这里就是比较版本是否一致,不一致则不导入字节码,而是会重新编译py文件
if (!check_api_version(name, module_api_version)) {
return NULL;
}
...
//创建一个PyModuleObject
if ((m = (PyModuleObject*)PyModule_New(name)) == NULL)
return NULL;
if (module->m_size > 0) {
m->md_state = PyMem_MALLOC(module->m_size);
if (!m->md_state) {
PyErr_NoMemory();
Py_DECREF(m);
return NULL;
}
memset(m->md_state, 0, module->m_size);
}
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;
}
根据上面的代码我们可以得出如下信息:
name:module的名称,在这里就是__builtins__
module_api_version:python内部使用的version值,用于比较。
PyModule_New:用于创建一个PyModuleObject对象
methods:该module中所包含的函数的集合,在这里,是builtin_methods
PyModule_AddFunctions:设置methods中的函数操作
PyModule_SetDocString:设置docstring
创建module对象
我们说python中的module在底层cpython中就是一个PyModuleObject对象,我们来看看这个对象长什么样子吧。
//moduleobject.c
typedef struct {
PyObject_HEAD
PyObject *md_dict;
struct PyModuleDef *md_def;
void *md_state;
PyObject *md_weaklist;
PyObject *md_name; /* for logging purposes after md_dict is cleared */
} PyModuleObject;
而这个对象我们知道是通过PyModule_New创建的。
//moduleobject.c
PyObject *
PyModule_New(const char *name)
{
//module name,和PyModuleObject
PyObject *nameobj, *module;
nameobj = PyUnicode_FromString(name);
if (nameobj == NULL)
return NULL;
//传入module name,拿到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;
//设置属性
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,并处理每一项元素,我们还是来看看长什么样子。
static PyMethodDef builtin_methods[] = {
{"__build_class__", (PyCFunction)builtin___build_class__,
METH_FASTCALL | METH_KEYWORDS, build_class_doc},
{"__import__", (PyCFunction)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)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)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", builtin_iter, METH_VARARGS, iter_doc},
BUILTIN_LEN_METHODDEF
BUILTIN_LOCALS_METHODDEF
{"max", (PyCFunction)builtin_max, METH_VARARGS | METH_KEYWORDS, max_doc},
{"min", (PyCFunction)builtin_min, METH_VARARGS | METH_KEYWORDS, min_doc},
{"next", (PyCFunction)builtin_next, METH_FASTCALL, next_doc},
BUILTIN_OCT_METHODDEF
BUILTIN_ORD_METHODDEF
BUILTIN_POW_METHODDEF
{"print", (PyCFunction)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},
};
怎么样,是不是看到了玄机。
总结一下就是:PyInterpreterState_New
创建PyInterpreterState
,通过传递PyInterpreterState
调用PyThreadState_New
得到PyThreadState
对象。接着就是我们熟悉的内建对象初始化,然后在_Py_InitializeCore_impl
中调用_PyBuiltin_Init
设置内建属性,中间调用了_PyModule_CreateInitialized
,在_PyModule_CreateInitialized
里面,先是使用PyModule_New
创建一个PyModuleObject,然后在里面设置了name和doc,然后使用PyModule_AddFunctions
设置methods,在这里面我们看到一大部分我们熟悉的内置属性,当设置完之后,回到_PyBuiltin_Init
,然后再设置一部分属性。至此,__builtins__
就完成了。
另外builtin_methods
里面是一个个的PyMethodDef
,_PyModule_CreateInitialized
都会基于它创建一个PyCFunctionObject
对象,这个对象是python中对函数指针的包装,当然还将这个函数指针和其它信息联系在了一起。
//methodobject.h
typedef struct {
PyObject_HEAD
PyMethodDef *m_ml; /* Description of the C function to call */
PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */
PyObject *m_module; /* The __module__ attribute, can be anything */
PyObject *m_weakreflist; /* List of weak references */
} PyCFunctionObject;
//methodobject.c
PyObject *
PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)
{
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;
_PyObject_GC_TRACK(op);
return (PyObject *)op;
}
很显然,python对PyCFunctionObject对象采取了缓冲池的策略,不过有了对PyLongObject和PyListObject对象的剖析,我们已经可以将策略猜出个八九不离十了。PyCFunctionObject中的那个m_self,就是module本身。而m_module存放的则是提个PyUnicodeObject对象,对应这个PyModuleObject的名字。
在_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;
}
//pylifecycle.c
_PyInitError
_Py_InitializeCore_impl(PyInterpreterState **interp_p,
const _PyCoreConfig *core_config)
{
...
PyObject *bimod = _PyBuiltin_Init();
if (bimod == NULL)
return _Py_INIT_ERR("can't initialize builtins modules");
_PyImport_FixupBuiltin(bimod, "builtins", modules);
interp->builtins = PyModule_GetDict(bimod);
...
}
以后python在需要访问__builtins__
时,直接访问interp->builtins就可以了,不需要再到interp->modules路面去找了。因为对于内置函数、属性的使用在python中会比较频繁,所以这种加速机制是很有效的。
14.2.2 创建sys module
14.2.2.1 sys module的备份
python在创建并设置了__builtins__
之后,会照猫画虎,用同样的流程来设置sys module,并像设置interp->builtins
一样设置interp->sysdict
//pylifecycle.c
_PyInitError
_Py_InitializeCore_impl(PyInterpreterState **interp_p,
const _PyCoreConfig *core_config)
{
PyObject *sysmod;
err = _PySys_BeginInit(&sysmod);
Py_INCREF(interp->sysdict);
PyDict_SetItemString(interp->sysdict, "modules", modules); //设置,不需要多说
_PyImport_FixupBuiltin(sysmod, "sys", modules);//这个是用来备份的,为什么要有这一步呢?后面说
}
_PySys_BeginInit
函数主要是在sys模块中加入python的基本信息、如python版本、api版本等等。但是为什么要进行备份呢,首先interp->sysdict和interp->sysdict指向的都是一个PyDictObject,而非PyModuleObject。由于python的module集合interp->modules是一个PyDictObject对象,而PyDictObject在python中是一个可变对象,所以其中维护的(module name, PyModuleObject)有可能在运行时被删除。对于python扩展的module,比如我们import numpy,为了避免多次引用,python会将所有扩展的module通过一个全局的PyDIctObject对象来进行备份维护。这个备份的动作就是通过上面源码中的_PyImport_FixupBuiltin
完成的
//import.c
int
_PyImport_FixupBuiltin(PyObject *mod, const char *name, PyObject *modules)
{
int res;
PyObject *nameobj;
nameobj = PyUnicode_InternFromString(name);
if (nameobj == NULL)
return -1;
res = _PyImport_FixupExtensionObject(mod, nameobj, nameobj, modules);
Py_DECREF(nameobj);
return res;
}
static PyObject *extensions = NULL;
int
_PyImport_FixupExtensionObject(PyObject *mod, PyObject *name,
PyObject *filename, PyObject *modules)
{
PyObject *dict, *key;
struct PyModuleDef *def;
int res;
//这个extensions是个全局变量
//如果为NULL,那么会创建一个PyDictObject对象
if (extensions == NULL) {
extensions = PyDict_New();
if (extensions == NULL)
return -1;
}
//获取mod中的md_def域,这是个PyMethodDef,而这里的mod就是sys module
def = PyModule_GetDef(mod);
//设置name和mod的对应关系
if (PyObject_SetItem(modules, name, mod) < 0)
return -1;
if (def->m_size == -1) {
if (def->m_base.m_copy) {
/* Somebody already imported the module,
likely under a different name.
XXX this should really not happen. */
Py_CLEAR(def->m_base.m_copy);
}
dict = PyModule_GetDict(mod); //拿到sys module
if (dict == NULL)
return -1;
def->m_base.m_copy = PyDict_Copy(dict);//将sys module这个PyDictObject进行拷贝
if (def->m_base.m_copy == NULL)
return -1;
}
key = PyTuple_Pack(2, filename, name);
if (key == NULL)
return -1;
//将存有拷贝的新的dict的PyMethodDef存储在extensions中
res = PyDict_SetItem(extensions, key, (PyObject *)def);
return 0;
}
我们看到python内部维护了一个全局变量extensions,这个变量在第一次调用_PyImport_FixupExtensionObject
时,会被创建为一个PyDictObject,而且这个PyDictObject对象就是所有已经被python加载的module组成的dict的一个备份
。当python系统的module集合中的某个标准扩展module被删除后不久又被重新加载时,python就不需要重新初始化这些module,只需要使用extensions这个用来备份的PyDictObject,将里面的module拿出来即可。因为这就意味着python中的标准扩展module是不会在运行时动态改变的,而事实也是如此。
所以我们看到,sys本身是一个module(我们说 sys module指的是sys这个模块)
,但是sys.modules
是一个字典,里面是module name和PyModuleObject组合起来的多个entry,当然 sys也在里面。而extensions则是用于备份的,一旦删除再次导入的时候,直接从extensions里面获取,然后加载到sys.module里面即可。
14.2.2.2 设置module
python在创建了sys module之后,会在此module中设置一个python搜索module时的默认路径集合。
//pylifecycle.c
_PyInitError
_Py_InitializeMainInterpreter(PyInterpreterState *interp,
const _PyMainInterpreterConfig *config)
{
...
err = add_main_module(interp);
...
}
//sysmodule.c
void
PySys_SetPath(const wchar_t *path)
{
PyObject *v;
if ((v = makepathobject(path, DELIM)) == NULL)
Py_FatalError("can't create sys.path");
if (_PySys_SetObjectId(&PyId_path, v) != 0)
Py_FatalError("can't assign sys.path");
Py_DECREF(v);
}
static PyObject *
makepathobject(const wchar_t *path, wchar_t delim)
{
int i, n;
const wchar_t *p;
PyObject *v, *w;
n = 1;
p = path;
while ((p = wcschr(p, delim)) != NULL) {
n++;
p++;
}
v = PyList_New(n);
if (v == NULL)
return NULL;
for (i = 0; ; i++) {
p = wcschr(path, delim);
if (p == NULL)
p = path + wcslen(path); /* End of string */
w = PyUnicode_FromWideChar(path, (Py_ssize_t)(p - path));
if (w == NULL) {
Py_DECREF(v);
return NULL;
}
PyList_SET_ITEM(v, i, w);
if (*p == ' ')
break;
path = p+1;
}
return v;
}
int
_PySys_SetObjectId(_Py_Identifier *key, PyObject *v)
{
PyThreadState *tstate = PyThreadState_GET();
PyObject *sd = tstate->interp->sysdict;
if (v == NULL) {
if (_PyDict_GetItemId(sd, key) == NULL)
return 0;
else
return _PyDict_DelItemId(sd, key);
}
else
return _PyDict_SetItemId(sd, key, v);
}
最终Python会创建一个PyListObject对象,这个list中包含了一组PyUnicodeObject,每一个PyUnicodeObject的内容就代表了一个搜索路径,就是python中的sys.path。而这个代表路径搜索集合的PyListObject对象也会被设置到sys module里面
python随后还会进行一些琐碎的动作,包括初始化python的import环境,初始化内建异常。初始化内建异常就是调用PyType_Ready
初始化各个异常类,而初始化import环境会在下一章剖析,这里不再详述
14.2.3 创建_main_ module
python中存在一个非常特殊的module,也就是__main__
。这个__main__
估计不用我多说了,我们之前说在PyModule_New
中,创建一个module之后,会在这个module对应的PyModuleObject对象的PyDictObject对象md_dict中插入一个名为__name__
的key,而value就是__main__
,都是PyUnicodeObject。但是对于当前模块来说,这个模块也可以叫做__main__
a = "我是谁?我在哪?我在干什么?"
from __main__ import a as hahaha
print(hahaha) # 我是谁?我在哪?我在干什么?
我们发现这样也是可以导入的,因为这个__main__
就是这个模块本身。
//pylifecycle.c
static _PyInitError
add_main_module(PyInterpreterState *interp)
{
PyObject *m, *d, *loader, *ann_dict;
//创建__main__ module,并将其插入到interp->modules中
m = PyImport_AddModule("__main__");
if (m == NULL)
return _Py_INIT_ERR("can't create __main__ module");
//获取__main__ module的dict
//我们说sys.modules里面是{"模块名": "模块"....},但是模块(PyModuleObject)也是维护了个字典
d = PyModule_GetDict(m);
//获取interp->modules中的__builtins__ module
if (PyDict_GetItemString(d, "__builtins__") == NULL) {
PyObject *bimod = PyImport_ImportModule("builtins");
if (bimod == NULL) {
return _Py_INIT_ERR("Failed to retrieve builtins module");
}
//将("__builtins__", __builtins__)插入到__main_ module的dict中
if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {
return _Py_INIT_ERR("Failed to initialize __main__.__builtins__");
}
Py_DECREF(bimod);
}
}
因此我们算是知道了,为什么python xxx.py执行的时候,__name__
是__main__
了,因为我们这里设置了,而python沿着命名空间寻找的时候,最终会在__main__
中发现__name__
,且值为__main__
。但如果是以import的方式加载的,那么__name__
则不是__main__
,而是模块名,这就意味着在找到__main__
之前,就已经在某个命名空间找到__name__
了。但这就奇怪了,无缘无故的,怎么会找到__main__
呢?别着急后面我们会揭晓。
其实这个__main__
我们是再熟悉不过的了,当输入dir()
的时候,就会显示__main__
的内容。dir是可以不加参数的,如果不加参数,那么默认访问当前的py文件,也就是__main__
。
所以说,访问模块就类似访问变量一样。modules里面存放了所有的(module name, PyModuleObject),当我们调用np的时候,是会找到name为"numpy"的值,然后这个值里面也维护了一个字典,其中就有一个key为__name__
的entry。
14.2.4 设置site-specific的module的搜索路径
python是一个非常开放的体系,它的强大来源于丰富的第三方库,这些库由外部的py文件来提供,当使用这些第三方库的时候,只需要简单的进行import即可。一般来说,这些第三方库都放在<sys.prefix>/lib/site-packages
中,如果程序想使用这些库,直接把库放在这里面即可。
但是到目前为止,python初始化的时候,我们只见到唯一一个与初始化路径集合相关的动作。PySys_SetPath
,但是我们好像也没看到python将site-packages
路径设置到搜索路径里面去啊。其实在完成了__main__
的创建之后,python才腾出手来,收拾这个site-package。这个关键的动作在于python的一个标准库:site.py
我们先来将Lib目录下的site.py删掉,然后导入一个第三方模块,看看会有什么后果
我们发现直接就崩溃了,吓得我赶紧把文件还原了。
因此我们发现,python在初始化的过程中却是进入了site.py,所以才有了如下的输出。而这个site.py也正是python能正确加载位于site-packages目录下tornado的关键所在。我们可以猜测,应该就是这个site.py将site-packages目录加入到了前面的sys.path中,而这个动作是由initsite完成的。
//pylifecycle.c
_PyInitError
_Py_InitializeMainInterpreter(PyInterpreterState *interp,
const _PyMainInterpreterConfig *config)
{
if (!Py_NoSiteFlag) {
err = initsite(); /* Module site */
}
static _PyInitError
initsite(void)
{
PyObject *m;
m = PyImport_ImportModule("site");
if (m == NULL) {
//这个报错信息,和我们上面图中是不是一样的呢?
return _Py_INIT_USER_ERR("Failed to import the site module");
}
Py_DECREF(m);
return _Py_INIT_OK();
}
在initsite中,只调用了PyImport_ImportModule
函数,这个函数是python中import机制的核心所在,将在下一章剖析。我们只需要知道PyImport_ImportModule("numpy")
等价于python中的import numpy
即可
14.3 激活python虚拟机
python运行方式有两种,一种是在命令行中运行的交互式环境;另一种则是以python xxx.py方式运行脚本文件。尽管方式不同,但是却殊途同归,进入同一个字节码虚拟机。
python在Py_Initialize(这个我们好像没有说,其是就是最先调用的函数,一层一层调用到我们说的那个,只不过我们直接跳到中间的重点函数去了)
完成之后,最终会通过pymain_run_file
调用PyRun_AnyFileExFlags
//pylifecycle.c
void
Py_Initialize(void)
{
Py_InitializeEx(1);
}
void
Py_InitializeEx(int install_sigs)
{
...
err = _Py_InitializeFromConfig(&config);
...
}
_PyInitError
_Py_InitializeFromConfig(const _PyCoreConfig *config)
{
...
err = _Py_InitializeCore(&interp, config);
...
}
//Modules/main.c
static int
pymain_run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
{
PyObject *unicode, *bytes = NULL;
const char *filename_str;
int run;
//如果有文件名,那么获取文件名
if (filename) {
unicode = PyUnicode_FromWideChar(filename, wcslen(filename));
...
}
//否则执行传入一个<stdin>,显然就是标准输入
else {
filename_str = "<stdin>";
}
//调用PyRun_AnyFileExFlags
run = PyRun_AnyFileExFlags(fp, filename_str, filename != NULL, p_cf);
Py_XDECREF(bytes);
return run != 0;
}
//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脚本式走的两条不同的路径,但是别着急,最终你会看到它们又会分久必合、走向同一条路径。
14.3.1 交互式运行
先来看看交互式运行时候的情形,不过在此之前先来看一下提示符。
我们每输入一行,开头都是>>>
,这个是sys.ps1,而输入语句块的时候,没输入完的时候,那么显示...
,这个是sys.ps2。如果修改了,那么就是我们自己定义的了。
//pythonrun.c
int
PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
{
PyObject *filename, *v;
int ret, err;
PyCompilerFlags local_flags;
filename = PyUnicode_DecodeFSDefault(filename_str);
//创建交互式提示符
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);
...
} 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__);
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);
...
//获取<module __main__>中维护的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命名空间。
14.3.2 脚本文件运行方式
接下来,我们看一看直接运行脚本文件的方式。
//compile.h
#define Py_file_input 257
//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);
if (maybe_pyc_file(fp, filename, ext, closeit)) {
//如果是pyc
FILE *pyc_fp;
/* Try to run a pyc file. First, re-open in binary */
if (closeit)
fclose(fp);
//二进制模式打开
if ((pyc_fp = _Py_fopen(filename, "rb")) == NULL) {
fprintf(stderr, "python: Can't reopen .pyc file
");
goto done;
}
if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) {
fprintf(stderr, "python: failed to set __main__.__loader__
");
ret = -1;
fclose(pyc_fp);
goto done;
}
v = run_pyc_file(pyc_fp, filename, d, d, flags);
} else {
/* When running from stdin, leave __main__.__loader__ alone */
if (strcmp(filename, "<stdin>") != 0 &&
set_main_loader(d, filename, "SourceFileLoader") < 0) {
fprintf(stderr, "python: failed to set __main__.__loader__
");
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_ty mod;
PyArena *arena = NULL;
PyObject *filename;
filename = PyUnicode_DecodeFSDefault(filename_str);
if (filename == NULL)
goto exit;
arena = PyArena_New();
if (arena == NULL)
goto exit;
//编译
mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
flags, NULL, arena);
if (closeit)
fclose(fp);
if (mod == NULL) {
goto exit;
}
//执行
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。
14.3.3 启动虚拟机
是的你没有看错,下面才是启动虚拟机,之前做了那么工作都是前戏。
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;
//创建PyFrameObject,执行PyCodeObject对象中的字节码指令序列
v = PyEval_EvalCode((PyObject*)co, globals, locals);
Py_DECREF(co);
return v;
}
run_mod接手传来的ast,然后传到PyAST_CompileObject中,然后完成了字节码的编译,最终创建了一个我们已经非常熟悉的PyCodeObject对象。关于这个完整的编译过程,就又是另一个话题了,总之先是scanner进行词法分析、将源代码切分成一个个的token,然后parser在词法分析之后的结果之上进行语法分析、通过切分好的token生成抽象语法树(AST,abstract syntax tree),然后将AST编译字节码,虚拟机再执行。知道这么一个大致的流程即可,至于到底是怎么分词、怎么建立语法树的,这就又是一个难点了,个人觉得甚至比研究python虚拟机还难。有兴趣的话可以去看python源码中Parser目录,如果能把python的分词、语法树的建立给了解清楚,那我觉得你完全可以手写一个正则表达式的引擎、以及各种模板语言。
而接下来,python已经做好一切工作,开始通过PyEval_EvalCode着手唤醒字节码虚拟机
//ceval.c
PyObject *
PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
{
return PyEval_EvalCodeEx(co,
globals, locals,
(PyObject **)NULL, 0,
(PyObject **)NULL, 0,
(PyObject **)NULL, 0,
NULL, NULL);
}
PyObject *
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
PyObject *const *args, int argcount,
PyObject *const *kws, int kwcount,
PyObject *const *defs, int defcount,
PyObject *kwdefs, PyObject *closure)
{
return _PyEval_EvalCodeWithName(_co, globals, locals,
args, argcount,
kws, kws != NULL ? kws + 1 : NULL,
kwcount, 2,
defs, defcount,
kwdefs, closure,
NULL, NULL);
}
PyObject *
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
PyObject *const *args, Py_ssize_t argcount,
PyObject *const *kwnames, PyObject *const *kwargs,
Py_ssize_t kwcount, int kwstep,
PyObject *const *defs, Py_ssize_t defcount,
PyObject *kwdefs, PyObject *closure,
PyObject *name, PyObject *qualname)
{
...
retval = PyEval_EvalFrameEx(f,0);
}
从操作系统创建进程,进程创建线程,线程设置builtins(包括设置__name__
、内建对象、内置函数方法等等)、设置缓冲池,然后PyType_Ready初始化,设置搜索路径。然后激活虚拟机,分词、编译、执行。而执行的这个过程就是曾经与我们朝夕相处的PyEval_EvalFrameEx
,掌控python世界中无数对象的生生灭灭。参数f就是PyFrameObject对象,我们曾经探索了很久,现在一下子就回到了当初。有种梦回栈帧对象
的感觉。目前的话,python的骨架我们已经看清了,虽然还有很多细节隐藏在幕后。至少神秘的面纱已经被撤掉了。
14.3.4 命名空间
现在我们来看一下有趣的东西,看看激活字节码虚拟机、在创建PyFrameObject对象时,所设置的3个命名空间:local、global、builtin
//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_GetItemId(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);
}
...
f->f_builtins = builtins;
...
//设置global命名空间
f->f_globals = globals;
//设置local命名空间
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 = globals; //如果locals为NULL,那么等同于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;
}
我们在python的层面上,看一个更加奇怪的现象
# 代表了globals()里面存放了builtins
print(globals()["__builtins__"]) # <module 'builtins' (built-in)>
# 我们说builtins里面包含了所有的内置对象、函数等等,显然调用int是可以的
print(globals()["__builtins__"].int("123")) # 123
# 但是,我居然能从builtins里面拿到globals
# 不过也很好理解,因为globals是一个内置函数,肯定是在builtins里面
print(globals()["__builtins__"].globals) # <built-in function globals>
# 于是拿到了globals,继续调用,然后获取__builtins__,又拿到了builtins,而且我们是可以调用list的
print(globals()["__builtins__"].globals()["__builtins__"].list("abcd")) # ['a', 'b', 'c', 'd']
为什么会出现这个情况,看样子我们似乎可以无限下去,不考虑内存、栈溢出等情况。而且从global命名空间获取builtins、从builtins里面获取global,这难道不会冲突吗?然而事实上,我有点偷换概念了,如果仔细想想的就能明白。
我们从builtins里面获取global命名空间是通过什么形式获取的,通过globals,而这个globals是一个函数啊。同理从源码中我们也看到了,创建builtins,还需要用到globals。
可以看到builtins和globals里面都存储一个能够获取对方空间的一个函数指针, 所以这两者是并不冲突的。当然除此之外,还有一个__name__
,注意我们之前说设置__name__
只是builtins的__name__
,并不是模块的。
# 我们看到,builtins里面获取的__name__居然不是__main__,而是builtins
print(globals()["__builtins__"].__name__) # builtins
# 首先是按照local global builtins的顺序查找是没问题的
# 而对于模块来说,我们知道locals为NULL,然后直接把globals赋值给locals了
# 而local里面有__name__,就是__main__,所以__main__和builtins.__name__不是一个东西
print(globals()["__name__"]) # __main__
# 初始化builtins的时候,那个__name__指的是builtins这个PyModuleObject的__name__
# 而对于我们py文件这个模块来说,__name__是设置在global命名空间里面的