Pocket Readings

个人阅读清单记录博客

0%

《深度剖析CPython解释器》24. Python运行时环境的初始化、源码分析Python解释器在启动时都做了哪些事情? - 古明地盆 - 博客园

PyThreadState * PyThreadState_New(PyInterpreterState *interp) { //我们注意到这个函数接收一个PyInterpreterState //这些说明了线程是依赖于进程的,因为需要进程分配资源,而且这个函数又调用了new_threadstate //除了传递PyInterpreterState之外,还传了



Tags:



via Pocket https://ift.tt/3ppqTxc original site



February 16, 2021 at 10:15PM

Comments


from: github-actions[bot] on: 3/3/2021

《深度剖析CPython解释器》24. Python运行时环境的初始化、源码分析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 是对线程的模拟。我们之前分析虚拟机的时候说过其执行环境,如果再将运行时环境加进去的话。

线程环境的初始化

在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 关联了 PyInterpreterStatePyInterpreterState 也关联了 PyInterpreterState 。到目前为止,仅有的两个对象建立起了联系。对应到Windows,或者说操作系统,我们说进程和线程建立了联系

PyInterpreterStatePyThreadState 建立了联系之后,那么就很容易在 PyInterpreterStatePyThreadState 之间穿梭。并且在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 算是有了一个阶段性的成功,我们创建了代表进程和线程概念的 PyInterpreterStatePyThreadState 对象,并且在它们之间建立的联系。下面, new_interpreter 将进行入另一个环节,设置系统module。

创建__builtins__

new_interpreter 中当Python解释器创建了 PyInterpreterStatePyThreadState 对象之后,就会开始设置系统的__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__“,而是模块名,后面我们会继续说。

2.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删掉,然后导入一个第三方模块,看看会有什么后果。

因此我们发现,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 = "<unprintable file name>";
        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;
    }
    //获取<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名字空间。

脚本文件运行方式

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

//.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, "<stdin>") != 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__"])  # <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']

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

可以看到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

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

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

小结

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

posted @ 2020-09-09 21:39 古明地盆 阅读(401) 评论(3) 编辑 收藏