Python内核阅读(十七): 运行环境初始化

Python 2017-08-25

起步

前面虚拟机的行为分析大半. 为了更进一步了解整个python运行时的行为. 从起点开始, python程序被执行, 一步一步的跟随python的踪迹. 真正有意义的初始化从 _Py_InitializeCore 开始的.

线程模型

在对Python初始化介绍之前, 需要先知道python的运行模式, 或者说线程模型. 在前面将指令调用函数有稍微提到这个线程模型. 对此,要重新认识一下线程:

[pystate.h]
typedef struct _is {

    struct _is *next;
    struct _ts *tstate_head;// 模拟进程环境中的线程集合

    int64_t id;

    PyObject *modules;
    PyObject *modules_by_index;
    PyObject *sysdict;
    PyObject *builtins;
    PyObject *importlib;

    ...
} PyInterpreterState;

typedef struct _ts {
    struct _ts *prev;
    struct _ts *next;
    PyInterpreterState *interp;

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

PyInterpreterState 结构是对进程的模拟, 而 PyThreadState 是对线程的模拟, 这个结构组成了用来运行字节码的巨大的for和switc的:

for(;;) {
    ...
    switch(opcode) {
        ...
    }
    ...
}

以及进程, 线程, 栈帧布局大致关系:

进程->next 进程 -> next 进程
 \
  线程->next 线程 -> next 线程
   |                       |
  栈帧(frame)             ...
   |
  f_back
   |
  f_back
   |
  ...

线程环境初始化

当执行一个可执行文件时, 操作系统首先会创建一个进程内核对象, 同样, python模拟的进程也要, 通过 PyInterpreterState_New 来创建一个 PyInterpreterState 对象:

[pystate.c]
static PyInterpreterState *interp_head = NULL;
static int64_t _next_interp_id = -1;

PyInterpreterState * PyInterpreterState_New(void)
{
    PyInterpreterState *interp = (PyInterpreterState *) PyMem_RawMalloc(sizeof(PyInterpreterState));
    if (interp != NULL) {
        HEAD_INIT();
        interp->modules = NULL;
        interp->modules_by_index = NULL;
        interp->sysdict = NULL;
        ... // 域初始很多null
        HEAD_LOCK();
        interp->next = interp_head;
        if (interp_main == NULL) {
            interp_main = interp;
        }
        interp_head = interp;
        if (_next_interp_id < 0) {
            PyErr_SetString(PyExc_RuntimeError,
                            "failed to get an interpreter ID");
            interp = NULL;
        } else {
            interp->id = _next_interp_id;
            _next_interp_id += 1;
        }
        HEAD_UNLOCK();
    }
    return interp;
}

在Python的运行时环境中, 创建的进程对象会通过 next 域形成一个链表, 表头就是 interp_head . _next_interp_id 是进程的一个编号, 依次递增. 得到进程对象后还需要创建一个全新的线程对象, 一个进程至少包含一个线程:

[pystate.c]
PyThreadState * PyThreadState_New(PyInterpreterState *interp)
{
    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) {   // 在线程对象中关联进程对象
        tstate->interp = interp;

        tstate->frame = NULL;
        tstate->recursion_depth = 0;
        tstate->overflowed = 0;
        ... // 域初始很多null

        if (init)
            _PyThreadState_Init(tstate);

        HEAD_LOCK();
        tstate->prev = NULL;
        tstate->next = interp->tstate_head;
        if (tstate->next)
            tstate->next->prev = tstate;
        interp->tstate_head = tstate;   // 在进程对象中关联线程
        HEAD_UNLOCK();
    }

    return tstate;
}

可以看出在 PyThreadState 也有 next 指针, 线程的next指针都设为了关联的进程对象的 tstate_head , 而tstate_head 是进程对象中用来存放线程的列表, 这部分和多线程有关. 进程对象和线程对象都彼此建立了关系, 使得在他们之间穿梭变得很容易.

在python运行环境中, 有一个全局变量 _PyThreadState_Current ,这个变量用来维护当前活动的线程. 初始时为NULL:

[pystate.c]
_Py_atomic_address _PyThreadState_Current = {0};

[pyatomic.h]
typedef struct _Py_atomic_address {
    atomic_uintptr_t _value;
} _Py_atomic_address;

_Py_atomic_address 类型中声明了 _value原子变量 . 原子变量的操作是使用原子API进行的. 所谓的 原子操作 是指该操作绝不会在执行完毕前被任何其他任务或事件打断. 也就是说它是最小的执行单位, 没有比它更小的了. 所以在同时执行多个线程的情境中, 当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该对象。 对原子变量的操作都要通过系统提供的API来进行:

[pystate.c]
#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. 关于 _PyThreadState_Current 的修改可以说是线程安全的.

在python启动后的第一个 PyThreadState 对象后, 会调用 PyThreadState_Swap 来设置这个变量:

[pystate.c]
PyThreadState * PyThreadState_Swap(PyThreadState *newts)
{
    PyThreadState *oldts = GET_TSTATE();

    SET_TSTATE(newts);
    return oldts;
}

接着, python的初始化动作开始类型系统的初始化, 接下来的动作是全局变量 __builtins__ , 这个变量在创建新的 PyFrameObject 对象时发挥作用.

在此之后就是一些其他边边角角的杂碎的动作, 如 _PyLong_Init 里创建小整数池等. 而后还需要进入一个环节:设置系统module

系统 module 初始化

module的初始化简单的两句话完成:

interp->modules = PyDict_New();
bimod = _PyBuiltin_Init();

内建对象主要由 _PyBuiltin_Init 来完成, 在调用它之前 interp->modules 先创建一个dict对象, 这个对象将维护系统所有module, 这也是所有线程对象所共享的资源:

[bltinmodule.c]
PyObject * _PyBuiltin_Init(void)
{
    PyObject *mod, *dict, *debug;
    mod = PyModule_Create(&builtinsmodule);
    if (mod == NULL)
        return NULL;
    dict = PyModule_GetDict(mod);
    // 将所有内建类型加入到 __builtins__ 中
    #define SETBUILTIN(NAME, OBJECT) \
    if (PyDict_SetItemString(dict, NAME, (PyObject *)OBJECT) < 0)       \
        return NULL;

    SETBUILTIN("None",                  Py_None);
    SETBUILTIN("False",                 Py_False);
    SETBUILTIN("True",                  Py_True);
    SETBUILTIN("bool",                  &PyBool_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("str",                   &PyUnicode_Type);  
    ...

    return mod;
}

_PyBuiltin_Init 的功能就是设置好 __builtins__ 这个module. 模块的申请由 PyModule_Create 完成:

[modsupport.h]
#define PyModule_Create(module) \
        PyModule_Create2(module, PYTHON_API_VERSION)

[moduleobject.c]
PyObject * PyModule_Create2(struct PyModuleDef* module, int module_api_version)
{
    const char* name;
    PyModuleObject *m;
    ...
    name = module->m_name;
    m = (PyModuleObject*)PyModule_New(name);
    if (module->m_size > 0) {
        m->md_state = PyMem_MALLOC(module->m_size);
        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) {    // 设置文档说明
        if (PyModule_SetDocString((PyObject *) m, module->m_doc) != 0) {
            Py_DECREF(m);
            return NULL;
        }
    }
    m->md_def = module;
    return (PyObject*)m;
}

这部分做了两件事, 一个是创建module对象, 一个是将方法放置到刚创建的module中.

创建对象

module对象通过 PyModule_New 函数创建, 参数是模块名:

[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;

PyObject * PyModule_New(const char *name)
{
    PyObject *nameobj, *module;
    nameobj = PyUnicode_FromString(name);
    if (nameobj == NULL)
        return NULL;
    module = PyModule_NewObject(nameobj);
    Py_DECREF(nameobj);
    return module;
}

PyObject * PyModule_NewObject(PyObject *name)
{
    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();
    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;
}

可见, PyModule_NewObject 才是幕后创建模块对象的英雄, 创建后 m->md_dict 是个字典, 需要进行一些基础的设置 module_init_dict :

[moduleobject.c]
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;

    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;
}

也可以这么说, PyModuleObject 是对 PyDictObject 的包装. 所以, 在 PyModule_New 创建模块对象时, 虽然有放入基础的 __name____doc__ , 但还是算一个空的module, 没有包含操作和数据.

设置 module 对象

创建完module对象后, 将代码回溯到函数 PyModule_Create2 中的 m = (PyModuleObject*)PyModule_New(name); 之后, dict = PyModule_GetDict(mod); 就是获取module对象中的m_dict域:

[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;
}

接着就是通过 PyModule_AddFunctions((PyObject *) m, module->m_methods)builtin_methods 中的方法遍历, 加入到module中, 先来看看 builtin_methods :

[methodobject.h]
struct PyMethodDef {
    const char  *ml_name;   /* The name of the built-in function/method */
    PyCFunction ml_meth;    /* The C function that implements it */
    int         ml_flags;   /* Combination of METH_xxx flags, which mostly
                               describe the args expected by the C func */
    const char  *ml_doc;    /* The __doc__ attribute, or NULL */
};
typedef struct PyMethodDef PyMethodDef;

[bltinmodule.c]
static PyMethodDef builtin_methods[] = {
    ...
    {"dir",             builtin_dir,        METH_VARARGS, dir_doc},
    {"getattr",         (PyCFunction)builtin_getattr, METH_FASTCALL, getattr_doc},
    {"print",           (PyCFunction)builtin_print,      METH_FASTCALL | METH_KEYWORDS, print_doc},
    ...
    {NULL,              NULL},
}

这些都是python的内置函数, 对于每个 PyMethodDef 结构, python都会为它创建一个 PyCFunctionObject 对象, 这个对象是对函数指针的包装:

[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;
}

free_list 看得出来这边也使用了缓冲池策略. 参数中的 self 也就是调用时传入的module, func = PyCFunction_NewEx(fdef, (PyObject*)module, name); , 差点被参数列表搞混了. 也就是说每个PyCFunctionObject对象的域 m_self 都指向自己所属的module. 而 m_module 存放的则是一个PyUnicodeObject对象, 这个则是对应这PyModuleObject对象的名字.

未命名.png

小结一下, 初始化函数 _Py_InitializeCore 数里面, 先创建一个进程, 一个线程, 并进行关联; 对类型系统进行初始化主要调用 _Py_ReadyTypes 进行各个类型的Ready操作. 调用每个类型中的初始函数 *_Init() ; 为进程创建dict对象用来存放modules; 初始化内建函数和对象 __builtins__:

[pylifecycle.c]
void _Py_InitializeCore(const _PyCoreConfig *config)
{
    ... // 一些环境设置
    _PyInterpreterState_Init();
    interp = PyInterpreterState_New();  // 创建进程对象
    tstate = PyThreadState_New(interp); // 创建线程对象
    (void) PyThreadState_Swap(tstate);  // 设置当前活动线程指针

    _Py_ReadyTypes();   // 类型系统初始化

    // 各个类型的init函数
    if (!_PyFrame_Init())
        Py_FatalError("Py_InitializeCore: can't init frames");

    if (!_PyLong_Init())
        Py_FatalError("Py_InitializeCore: can't init longs");
    ...
    interp->modules = PyDict_New();
    bimod = _PyBuiltin_Init();      // 设置内建函数和对象
    interp->builtins = PyModule_GetDict(bimod);

    ....    // 后续动作
}

后续的动作也是很重要的, 设置完 interp->builtins ,照猫画虎的设置 interp->sysdict .

创建 sys module

[pylifecycle.c]
void _Py_InitializeCore(const _PyCoreConfig *config)
{
    ...
    interp->builtins = PyModule_GetDict(bimod);

    // sys模块
    sysmod = _PySys_BeginInit();
    interp->sysdict = PyModule_GetDict(sysmod);
    _PyImport_FixupBuiltin(sysmod, "sys");  // 备份sys模块
    PyDict_SetItemString(interp->sysdict, "modules", interp->modules);
    ...
}

_PySys_BeginInit 函数中主要是对 sys 模块中加入python的基本信息, 如python版本, copyright, api版本等.在完成对 __builtin__sys 两个module设置后, 他们在内存中的情形如图.

[示意图]

图中sys和builtin对象以虚线引出的 PyDictObject 对象表示PyModuleObject内部维护的字典对象,即md_dict. 从前面的分析可以看到 interp->builtinsinterp->sysdict 指向的确实是PyDictObject对象, 而不是PyModuleObject对象.

对于扩展的模块, 为了避免对sys再进行初始化, python会将所有扩展module通过一个全局的PyDIctObject对象来进行维护.备份sys的动作中 _PyImport_FixupExtensionObject 完成:

[import.c]
int _PyImport_FixupBuiltin(PyObject *mod, const char *name)
{
    int res;
    PyObject *nameobj;
    nameobj = PyUnicode_InternFromString(name);
    if (nameobj == NULL)
        return -1;
    res = _PyImport_FixupExtensionObject(mod, nameobj, nameobj);
    Py_DECREF(nameobj);
    return res;
}

备份的实际动作是 _PyImport_FixupExtensionObject :

[import.c]
static PyObject *extensions = NULL; // 全局变量, 一个dict对象

int _PyImport_FixupExtensionObject(PyObject *mod, PyObject *name, PyObject *filename)
{
    PyObject *modules, *dict, *key;
    struct PyModuleDef *def;
    int res;
    if (extensions == NULL) {   // 如果extensions为空, 创建dict对象
        extensions = PyDict_New();
    }

    def = PyModule_GetDef(mod); // 获得mod中的md_def域,是一个PyMethodDef. 这里的mod是 sysmod

    modules = PyImport_GetModuleDict();         // 获得interp->modules
    if (PyDict_SetItem(modules, name, mod) < 0) // 设置(name, mod)的对应关系
        return -1;
    if (_PyState_AddModule(mod, def) < 0) {
        PyDict_DelItem(modules, name);
        return -1;
    }
    if (def->m_size == -1) {
        if (def->m_base.m_copy) {
            Py_CLEAR(def->m_base.m_copy);
        }
        dict = PyModule_GetDict(mod);
        def->m_base.m_copy = PyDict_Copy(dict); // 对dict进行拷贝
        if (def->m_base.m_copy == NULL)
            return -1;
    }
    key = PyTuple_Pack(2, filename, name);
    if (key == NULL)
        return -1;
    res = PyDict_SetItem(extensions, key, (PyObject *)def); // 将存有拷贝的新的dict的PyMethodDef存储在extensions中
    Py_DECREF(key);
    if (res < 0)
        return -1;
    return 0;
}

python内部有个全局变量 extensions 用来存放所有已经被加载的module中的PyMethodDef. 当python谢勇的module集合中某个扩展被删除后又被重新加载时, python就不需要再次为其初始化了. 只需要用extensions中备份的PyMethodDef即可. 后文再说明.

设置 module 搜索路径

在此需要为如 import xxx 设置搜索顺序:

[pylifecycle.c]
int _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *config)
{
    ...
    PySys_SetPath(Py_GetPath());
    ...
}

[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);
}

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);
}

省略了 makepathobject(path, DELIM) 的代码, 在它函数内, 会创建一个PyListObject对象, 这个list包含PyStringObject, 其顺序就是import模块时的搜索顺序

最终这个表示路径搜索顺序的list对呗放置到 interp->sysdict 中, 这个列表就是 sys.path 看到的.

回到 _Py_InitializeCore 函数的动作中, 再其备份 sys 模块后, 设置错误输出流 stderr . 然后初始化 import 机制. 关于import机制下一篇再说. 会有动态模块加载. 初始化import机制, 函数 _Py_InitializeCore 就算是完成工作了. 但对于python的初始化来说, 初始化还未完成. 不过也就剩一个步骤了, 就是内建异常初始化:

[pylifecycle.c]
int _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *config)
{
    ...
    if (_PySys_EndInit(interp->sysdict) < 0)
        Py_FatalError("Py_InitializeMainInterpreter: can't finish initializing sys");
    initexternalimport(interp);

    /* initialize the faulthandler module */
    if (_PyFaulthandler_Init())
        Py_FatalError("Py_InitializeMainInterpreter: can't initialize faulthandler");

    if (initfsencoding(interp) < 0)
        Py_FatalError("Py_InitializeMainInterpreter: unable to load the file system codec");

    if (config->install_signal_handlers)
        initsigs(); /* Signal handling stuff, including initintr() */

    if (_PyTraceMalloc_Init() < 0)
        Py_FatalError("Py_InitializeMainInterpreter: can't initialize tracemalloc");
    initmain(interp); /* Module __main__ */
    ...
    _Py_Initialized = 1;    // 初始化已完成
}

创建 main 模块

在python初始化接近尾声时, python将创建一个非常特殊的模块, 名为 "__main__" 的模块.

[pylifecycle.c]
static void initmain(PyInterpreterState *interp)
{
    PyObject *m, *d, *loader, *ann_dict;
    // 创建 __main__ 模块, 并将其插入至 interp->modules 中
    m = PyImport_AddModule("__main__");
    d = PyModule_GetDict(m);    // 获得 __main__ 模块中的dict
    ann_dict = PyDict_New();

    Py_DECREF(ann_dict);
    if (PyDict_GetItemString(d, "__builtins__") == NULL) {
        // 获得interp->modules中的 __builtins__
        PyObject *bimod = PyImport_ImportModule("builtins");
        // 将 ("__builtins__", builtin module) 插入到__mian__模块中
        if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {
            Py_FatalError("Failed to initialize __main__.__builtins__");
        }
        Py_DECREF(bimod);
    }

    // 设置 __loader__ 这部分讲解跳过
    loader = PyDict_GetItemString(d, "__loader__");
    if (loader == NULL || loader == Py_None) {
        PyObject *loader = PyObject_GetAttrString(interp->importlib,
        if (PyDict_SetItemString(d, "__loader__", loader) < 0) {
            Py_FatalError("Failed to initialize __main__.__loader__");
        }
        Py_DECREF(loader);
    }
}

这个 __main__ 模块,就是我们经常写的:

if __name__ == "__main__":
    pass

这样的单元测试机制. 在 PyImport_AddModule("__main__") 中, 创建一个名为 main 的模块, 并且并将这个名称已PyUnicodeObject的形式赋值给模块中dict的 __name__ . 这个模块作为主程序运行的python源文件被视为主模块.

当以 python xxx.py 的方式运行时, python在沿着名字空间查找 __name__ 时, 就会最终在 main 模块中发现name了. 而如果一个py文件是以import方式加载的, 那么该文件里的name不会是"main".

你现在是不是有疑惑, 根据变量搜索的LGB规则, 在builtin内建中是有name的, 为什么不会找到这里面的而是去main模块中找呢.

>>> getattr(__builtins__, '__name__')
'builtins'

这个的答案在名字空间初始化中.

设置 site-packages

一般来说, 第三方模块放在 %PythonHome%\Lib\site-packages 目录下, Python初始化过程中也有 PySys_SetPath(Py_GetPath()) 来设置模块的搜索路径, 但遗憾的事, site-packages 并不在其中.

设置这个路径到module的搜索路径中是 %PythonHome%\Lib\site.py .

[pylifecycle.c]
int _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *config)
{
    ...
    initmain(interp); /* Module __main__ */
    if (!Py_NoSiteFlag)
            initsite(); /* Module site */
}

static void initsite(void)
{
    PyObject *m;
    m = PyImport_ImportModule("site");
    if (m == NULL) {
        fprintf(stderr, "Failed to import the site module\n");
        PyErr_Print();
        Py_Finalize();
        exit(1);
    }
    else {
        Py_DECREF(m);
    }
}

initsite() 函数中, 只是调用了 PyImport_ImportModule 函数, 这个函数也就是python的import机制的核心所在.

激活虚拟机

在这步之前, python已经完成了执行程序所必须的基础设施, 通常我们以 python xxx.py 方式来启动, 在初始化 _Py_InitializeCore 函数后调用 run_file 最终通过 PyRun_AnyFileExFlags :

[Modules/main.c]
int Py_Main(int argc, wchar_t **argv)
{
    ...
    FILE *fp = stdin;   // 默认以stdin作为输入流
    _Py_InitializeCore(&core_config);
    ...
    _Py_ReadMainInterpreterConfig(&config);
    _Py_InitializeMainInterpreter(&config);
    ...
    if (sts==-1 && cmdline.filename != NULL) {  // 如果有指定py脚本
        fp = _Py_wfopen(cmdline.filename, L"r");
    }
    if (sts == -1)
        sts = run_file(fp, cmdline.filename, &cf);
    ...
    return sts;
}

static int run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
{
    PyObject *unicode, *bytes = NULL;
    char *filename_str;
    int run;
    ...
    if (filename) {
        unicode = PyUnicode_FromWideChar(filename, wcslen(filename));
        ...
    }
    else
        filename_str = "<stdin>";

    run = PyRun_AnyFileExFlags(fp, filename_str, filename != NULL, p_cf);
    Py_XDECREF(bytes);
    return run != 0;
}

当python指令后面没有跟上py文件名的时候, 文件名会默认设置为 filename_str = "<stdin>"; , 会为PyRun_AnyFileExFlags 传入, 这意思是以系统标准输入流stdin.

[pythonrun.c]
int PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit, PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    // 根据fp是否交互环境还是py脚本
    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);
}

python有两种启动模式, 一种是 python xxx.py 运行python脚本, 一种是单单 python 命令进入交互模式. 这个判断采用 Py_FdIsInteractive(fp, filename) 来进行区分. 如果是交互式, 那它就进入 PyRun_InteractiveLoopFlags 来等待下一个输入.

交互式运行方式

[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 = -1;
    // 进入交互式环境
    for (;;) {
        ret = PyRun_InteractiveOneObject(fp, filename, flags);
        _PY_DEBUG_PRINT_TOTAL_REFS();
        if (ret == E_EOF) {
            err = 0;
            break;
        }
    }
    Py_DECREF(filename);
    return err;
}

int PyRun_InteractiveOneObject(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 */
    ...
    v = _PySys_GetObjectId(&PyId_ps1);

    w = _PySys_GetObjectId(&PyId_ps2);
    // 编译交互环境中输入的语句
    arena = PyArena_New();

    mod = PyParser_ASTFromFileObject(fp, filename, enc,
                                     Py_single_input, ps1, ps2,
                                     flags, &errcode, arena);

    m = PyImport_AddModuleObject(mod_name); // 获得__main__ 模块
    if (m == NULL) {
        PyArena_Free(arena);
        return -1;
    }
    d = PyModule_GetDict(m);
    v = run_mod(mod, filename, d, d, flags, arena); // 执行用户的python语句
    PyArena_Free(arena);
    if (v == NULL) {
        PyErr_Print();
        flush_io();
        return -1;
    }
    Py_DECREF(v);
    flush_io();
    return 0;
}

这部分创建了交互环境用到的 ">>>""..." 的提示符, 调用 PyParser_ASTFromFileObject 对用户输入python语句进行编译, 其结果是结构与python语句一样的抽象语法树AST. 调用 run_mod 将最终完成对输入语句的执行. 这里的参数d就将作为当前活动的frame对象的local名字空间和global名字空间.

脚本文件运行方式

如果有执行运行的py脚本, 那么程序会调用 PyRun_SimpleFileExFlags(fp, filename, closeit, flags); :

[compile.h]
#define Py_file_input 257
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;

    m = PyImport_AddModule("__main__");
    Py_INCREF(m);
    d = PyModule_GetDict(m);
    if (PyDict_GetItemString(d, "__file__") == NULL) {// 设置__main__中__file__属性
        PyObject *f;
        f = PyUnicode_DecodeFSDefault(filename);
        PyDict_SetItemString(d, "__file__", f);
        set_file_name = 1;
    }
    len = strlen(filename);
    ext = filename + len - (len > 4 ? 4 : 0);
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        // 如果是pyc文件
    } else {
        ...
        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);
    arena = PyArena_New();
    if (arena == NULL)
        goto exit;
    // 编译
    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena);
    if (closeit)
        fclose(fp);
    // 执行
    ret = run_mod(mod, filename, globals, locals, flags, arena);

exit:
    Py_XDECREF(filename);
    if (arena != NULL)
        PyArena_Free(arena);
    return ret;
}

很显然, 脚本文件的执行流程和交互式方式有着相同的动作. 脚本文件一样经过编译最后进入 run_mod . 而且一样把 main 模块中的dict作为local名字空间和global名字空间传入. 是时候看看 run_mod 里发生什么了.

启动虚拟机

run_mod 开始, python就剩下启动字节码虚拟机了, 待执行的python代码都已经编译过了.

[pythonrun.c]
static PyObject * run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    PyCodeObject *co;
    PyObject *v;
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);
    if (co == NULL)
        return NULL;
    v = PyEval_EvalCode((PyObject*)co, globals, locals);
    Py_DECREF(co);
    return v;
}

在参数含有已经处理过的AST, 从中获得PyCodeObject对象, 开始通过 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 **args, int argcount, PyObject **kws, int kwcount,
           PyObject **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 **args, Py_ssize_t argcount,
           PyObject **kwnames, PyObject **kwargs,
           Py_ssize_t kwcount, int kwstep,
           PyObject **defs, Py_ssize_t defcount,
           PyObject *kwdefs, PyObject *closure,
           PyObject *name, PyObject *qualname)
{
    ...
    retval = PyEval_EvalFrameEx(f,0);
    return retval;
}

正如 <<虚拟机框架>> 那篇介绍的, PyEval_EvalFrameEx 就是推到多米诺骨牌的第一篇骨牌. 在此, python进程被创建, python字节码虚拟机被唤醒, 之后就是执行引擎循环往复的执行字节码了.

名字空间

对于主模块来说, 它的local名字空间等于global名字空间没什么问题. main 模块维护的dict就作为local的名字空间, 这也是为什么 __name__ 取的不是内建 __builtins__ 里的, 因为它在local名字空间就命中了.

在新创建的PyFunctionObject中设置的builtin名字空间实际上也是 __builtins__ , 这意味着python所有线程都共享builtin名字空间.


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

赏个馒头吧