zoukankan      html  css  js  c++  java
  • 《深度剖析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
    ",
                    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
    ");
                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 {
            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 = 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是动态语言,这就意味很多操作都要发生在运行时。

  • 相关阅读:
    LeetCode 230. 二叉搜索树中第K小的元素
    LeetCode 669. 修剪二叉搜索树
    LeetCode 94. 二叉树的中序遍历
    LeetCode 145. 二叉树的后序遍历
    LeetCode 144. 二叉树的前序遍历
    Not registered via @EnableConfigurationProperties or marked as Spring component
    maven依赖的报错Unable to import maven project: See logs for details
    GDIPlus的使用准备工作
    全局变量替代方案:控制反转,依赖注入
    MFC使用TRACKMOUSEEVENT触发mouseHover和mouseLeave
  • 原文地址:https://www.cnblogs.com/traditional/p/13642194.html
Copyright © 2011-2022 走看看