zoukankan      html  css  js  c++  java
  • 《python解释器源码剖析》第14章--python运行环境初始化

    14.0 序

    我们之前分析了python的核心--字节码、虚拟机的剖析工作。当时我们说过,这仅仅只是一部分,还有一部分内容被遮在了幕后。而这一章,我们将回到时间的起点,从python的应用程序被执行开始,一步一步紧紧跟随python的踪迹,完整地展示python在启动之初的所有动作。当我们跟随python完成所有的初始化动作之后,也就能对python执行引擎执行字节码指令时的整个运行环境了如指掌了。

    14.1 线程环境初始化

    14.1.1 线程模型回顾

    我们之前介绍栈帧的时候,说过python中的线程模型,python中的一个线程对应C中的一个线程,也对应操作系统中的一个原生线程。当时还强调,python中的线程和操作系统线程是对应的。但是每个线程都对应一个PyThreadState对象,记录了真正的执行线程的状态。而python在启动之后,初始化的动作是从_Py_InitializeCore开始的,然后这个函数调用了_Py_InitializeCore_impl进行完成初始化,我们分析会从_Py_InitializeCore_impl开始,当然_Py_InitializeCore里面也做了一些工作,具体的我们后面会介绍。下面先看看进程和线程在cpython中的定义。

    //pystate.h
    typedef struct _is PyInterpreterState;
    
    typedef struct _is {
        struct _is *next;
        struct _ts *tstate_head; //模拟进程环境中的线程集合
    
        int64_t id;
        int64_t id_refcount;
        PyThread_type_lock id_mutex;
    
        PyObject *modules;
        PyObject *modules_by_index;
        PyObject *sysdict;
        PyObject *builtins;
        PyObject *importlib;
    
        int check_interval;
    
        long num_threads;
        size_t pythread_stacksize;
    
        PyObject *codec_search_path;
        PyObject *codec_search_cache;
        PyObject *codec_error_registry;
        int codecs_initialized;
        int fscodec_initialized;
    
        _PyCoreConfig core_config;
        _PyMainInterpreterConfig config;
    #ifdef HAVE_DLOPEN
        int dlopenflags;
    #endif
    
        PyObject *builtins_copy;
        PyObject *import_func;
        _PyFrameEvalFunction eval_frame;
    
        Py_ssize_t co_extra_user_count;
        freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
    
    #ifdef HAVE_FORK
        PyObject *before_forkers;
        PyObject *after_forkers_parent;
        PyObject *after_forkers_child;
    #endif
        void (*pyexitfunc)(PyObject *);
        PyObject *pyexitmodule;
    
        uint64_t tstate_next_unique_id;
    } PyInterpreterState;
    #endif   
    
    
    typedef struct _ts PyThreadState;
    typedef struct _ts {
    
        struct _ts *prev;
        struct _ts *next;
        PyInterpreterState *interp;
    
        struct _frame *frame; //栈帧对象,模拟线程中函数的调用堆栈
        int recursion_depth;
        char overflowed; 
        char recursion_critical; 
        int stackcheck_counter;
    
        int tracing;
        int use_tracing;
    
        Py_tracefunc c_profilefunc;
        Py_tracefunc c_tracefunc;
        PyObject *c_profileobj;
        PyObject *c_traceobj;
    
        PyObject *curexc_type;
        PyObject *curexc_value;
        PyObject *curexc_traceback;
    
        _PyErr_StackItem exc_state;
    
        _PyErr_StackItem *exc_info;
    
        PyObject *dict; 
    
        int gilstate_counter;
    
        PyObject *async_exc; 
        unsigned long thread_id; 
    
        int trash_delete_nesting;
        PyObject *trash_delete_later;
    
        void (*on_delete)(void *);
        void *on_delete_data;
    
        int coroutine_origin_tracking_depth;
    
        PyObject *coroutine_wrapper;
        int in_coroutine_wrapper;
    
        PyObject *async_gen_firstiter;
        PyObject *async_gen_finalizer;
    
        PyObject *context;
        uint64_t context_ver;
    
        /* Unique thread state id. */
        uint64_t id;
    
        /* XXX signal handlers should also be here */
    
    } PyThreadState;
    #endif   /* !Py_LIMITED_API */
    

    其中PyInterpreterState是对进程的模拟,而PyThreadState则是对线程的模拟,下面还是我们之前画的,python虚拟机运行期间某个时刻整个的运行环境。

    14.1.2 初始化线程环境

    在Windows平台上,当执行一个可执行文件时,操作系统首先会创建一个进程内核对象。同理在python中也是如此,会在_Py_InitializeCore_impl中调用PyInterpreterState_New创建一个崭新的PyInterpreterState对象。

    //pystate.c
    PyInterpreterState *
    PyInterpreterState_New(void)
    {
        PyInterpreterState *interp = (PyInterpreterState *)
                                     PyMem_RawMalloc(sizeof(PyInterpreterState));
    
        if (interp == NULL) {
            return NULL;
        }
    
        interp->id_refcount = -1;
        interp->id_mutex = NULL;
        interp->modules = NULL;
        interp->modules_by_index = NULL;
        interp->sysdict = NULL;
        interp->builtins = NULL;
        interp->builtins_copy = NULL;
        interp->tstate_head = NULL;
        interp->check_interval = 100;
        interp->num_threads = 0;
        interp->pythread_stacksize = 0;
        interp->codec_search_path = NULL;
        interp->codec_search_cache = NULL;
        interp->codec_error_registry = NULL;
        interp->codecs_initialized = 0;
        interp->fscodec_initialized = 0;
        interp->core_config = _PyCoreConfig_INIT;
        interp->config = _PyMainInterpreterConfig_INIT;
        interp->importlib = NULL;
        interp->import_func = NULL;
        interp->eval_frame = _PyEval_EvalFrameDefault;
        interp->co_extra_user_count = 0;
    #ifdef HAVE_DLOPEN
    #if HAVE_DECL_RTLD_NOW
        interp->dlopenflags = RTLD_NOW;
    #else
        interp->dlopenflags = RTLD_LAZY;
    #endif
    #endif
    #ifdef HAVE_FORK
        interp->before_forkers = NULL;
        interp->after_forkers_parent = NULL;
        interp->after_forkers_child = NULL;
    #endif
        interp->pyexitfunc = NULL;
        interp->pyexitmodule = NULL;
    
        HEAD_LOCK();
        if (_PyRuntime.interpreters.next_id < 0) {
            /* overflow or Py_Initialize() not called! */
            PyErr_SetString(PyExc_RuntimeError,
                            "failed to get an interpreter ID");
            PyMem_RawFree(interp);
            interp = NULL;
        } else {
            interp->id = _PyRuntime.interpreters.next_id;
            _PyRuntime.interpreters.next_id += 1;
            interp->next = _PyRuntime.interpreters.head;
            if (_PyRuntime.interpreters.main == NULL) {
                _PyRuntime.interpreters.main = interp;
            }
            _PyRuntime.interpreters.head = interp;
        }
        HEAD_UNLOCK();
    
        if (interp == NULL) {
            return NULL;
        }
    
        interp->tstate_next_unique_id = 0;
    
        return interp;
    }
    

    关于进程方面我们不做过多解释,只需要知道python在运行的时候,会创建一个、或者多个PyInterpreterState(进程)对象,然后通过next指针将多个PyInterpreterState形成一个链表结构

    PyInterpreterState_New成功创建PyInterpreterState之后,会再接再厉,调用PyThreadState_New创建一个全新的PyThreadState(线程)对象。

    //pystate.c
    PyThreadState *
    PyThreadState_New(PyInterpreterState *interp)
    {	
        //我们注意到这个函数接收一个PyInterpreterState
        //表名线程是依赖于进程的,因为需要进程分配资源
        //具体的区别就不啰嗦了,而且这个函数又调用了new_threadstate
        //除了传递PyInterpreterState,还有传了一个1,想也不用想肯定是创建的线程数量
        //只有1个,也就是主线程(main thread)
        return new_threadstate(interp, 1);
    }
    
    static PyThreadState *
    new_threadstate(PyInterpreterState *interp, int init)
    {
        PyThreadState *tstate = (PyThreadState *)PyMem_RawMalloc(sizeof(PyThreadState));
    	
        //设置从线程中获取函数调用栈的操作
        if (_PyThreadState_GetFrame == NULL)
            _PyThreadState_GetFrame = threadstate_getframe;
    
        if (tstate != NULL) {
            //在PyThreadState对象中关联PyInterpreterState对象
            tstate->interp = interp;
    		
            //下面设置线程的属性,我们说了python中的线程是对操作系统线程的模拟
            //抽象成一些属性
            tstate->frame = NULL;
            tstate->recursion_depth = 0;
            tstate->overflowed = 0;
            tstate->recursion_critical = 0;
            tstate->stackcheck_counter = 0;
            tstate->tracing = 0;
            tstate->use_tracing = 0;
            tstate->gilstate_counter = 0;
            tstate->async_exc = NULL;
            tstate->thread_id = PyThread_get_thread_ident();
    
            tstate->dict = NULL;
    
            tstate->curexc_type = NULL;
            tstate->curexc_value = NULL;
            tstate->curexc_traceback = NULL;
    
            tstate->exc_state.exc_type = NULL;
            tstate->exc_state.exc_value = NULL;
            tstate->exc_state.exc_traceback = NULL;
            tstate->exc_state.previous_item = NULL;
            tstate->exc_info = &tstate->exc_state;
    
            tstate->c_profilefunc = NULL;
            tstate->c_tracefunc = NULL;
            tstate->c_profileobj = NULL;
            tstate->c_traceobj = NULL;
    
            tstate->trash_delete_nesting = 0;
            tstate->trash_delete_later = NULL;
            tstate->on_delete = NULL;
            tstate->on_delete_data = NULL;
    
            tstate->coroutine_origin_tracking_depth = 0;
    
            tstate->coroutine_wrapper = NULL;
            tstate->in_coroutine_wrapper = 0;
    
            tstate->async_gen_firstiter = NULL;
            tstate->async_gen_finalizer = NULL;
    
            tstate->context = NULL;
            tstate->context_ver = 1;
    
            tstate->id = ++interp->tstate_next_unique_id;
    
            if (init)
                _PyThreadState_Init(tstate);
    
            HEAD_LOCK();
            tstate->prev = NULL;
            tstate->next = interp->tstate_head;
            if (tstate->next)
                tstate->next->prev = tstate;
            //在PyInterpreterState对象关联PyThreadState对象
            interp->tstate_head = tstate;
            HEAD_UNLOCK();
        }
    
        return tstate;
    }
    

    PyInterpreterState_New相同,PyThreadState_New申请内存,创建PyThreadState对象,并且对其中各个域进行初始化。但是我们注意到,在PyThreadState结构体中,也存在着一个next指针,肯定会在python运行的某个时刻,如上面图中显示的那样,存在多个PyThreadState对象形成一个链表。显然用鼻子像也知道这是和python的多线程实现有关。

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

    PyInterpreterStatePyThreadState建立了联系之后,那么就很容易在PyInterpreterStatePyThreadState之间穿梭。并且在python运行时环境中,会有一个变量_PyThreadState_Current ,一直维护着当前活动的线程,更准确的说是当前活动线程对应的PyThreadState对象。初始时,该变量为NULL。在创建了python启动之后的第一个PyThreadState之后,会用该PyThreadState对象调用PyThreadState_Swap函数来设置这个变量

    //pystate.c
    PyThreadState *
    PyThreadState_Swap(PyThreadState *newts)
    {
        PyThreadState *oldts = GET_TSTATE();
    
        SET_TSTATE(newts);
        return oldts;
    }
    
    //pystate.h
    #define _PyThreadState_Current _PyRuntime.gilstate.tstate_current
    //pystate.c
    /*
    GET_TSTATE是一个带参数的宏,调用了_Py_atomic_store_relaxed
    */
    #define GET_TSTATE() 
        ((PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current))
    #define SET_TSTATE(value) 
        _Py_atomic_store_relaxed(&_PyThreadState_Current, (uintptr_t)(value))
    
    //pyatomic.h
    #define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL) 
        _Py_atomic_store_explicit(ATOMIC_VAL, NEW_VAL, _Py_memory_order_relaxed)
    
    #define _Py_atomic_store_explicit(ATOMIC_VAL, NEW_VAL, ORDER) 
        atomic_store_explicit(&(ATOMIC_VAL)->_value, NEW_VAL, ORDER)
    //而atomic_store_explicit是系统头文件stdatomic.h中定义的api,这是在系统的api中修改的,所以说是线程安全的
    

    接着,python初始化动作开始转向python类型的初始化,这个转折是在调用_Py_InitializeCore_impl中调用_Py_ReadyTypes时开始的。

    //Python/pylifecycle.c
    _PyInitError
    _Py_InitializeCore_impl(PyInterpreterState **interp_p,
                            const _PyCoreConfig *core_config)
    {
        PyInterpreterState *interp;
        _PyInitError err;
        ...
        ...    
        //创建进程    
        interp = PyInterpreterState_New();
        ...
        ...    
        //创建线程    
        PyThreadState *tstate = PyThreadState_New(interp);
    	
        //类型系统初始化
        _Py_ReadyTypes();
        
        if (!_PyFrame_Init())
            return _Py_INIT_ERR("can't init frames");
    
        if (!_PyLong_Init())
            return _Py_INIT_ERR("can't init longs");
    
        if (!PyByteArray_Init())
            return _Py_INIT_ERR("can't init bytearray");
    
        if (!_PyFloat_Init())
            return _Py_INIT_ERR("can't init float");
        ...
        ...    
    }
    

    类型系统的初始化是一套相当反复的动作,在介绍python的类机制时,我们已经介绍过了。紧接着,会调用各种XXX_Init()进行初始化,比如在PyLong_Init中会初始化我们在剖析python整数对象时看到的那个庞大的整数对象系统等等,这里就不再细看了,有兴趣的可以阅读源码。

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

    14.2 系统module初始化

    当我们想查看一个object支持哪些方法的时候,会使用内置函数dir(obj),然后显示一个list的内容,我们先不管它是如何运作、以及输出的信息是什么,我们单来看看这个调用本身就很有意思。我们知道python如果执行dir(),那么必定是在某个命名空间中找到了符号dir所对应的对象,而这还是一个callable的对象。不过我们先来考虑符号dir的存在性,根据使用python的经验,这个dir显然是可以直接使用的,这就代表了python启动之后就已经创建了一个命名空间,这个空间里面存在着符号dir。而这个命名空间就来自于系统module,这些module正是在_Py_InitializeCore_impl中设置的。其中第一个被python创建的module就是__builtins__

    14.2.1 创建_builtins_

    _Py_InitializeCore_impl,当python创建了PyInterpreterStatePyThreadState对象之后,就会开始通过_PyBuiltin_Init来设置系统的__builtins__了。

    //Python/pylifecycle.c
    _PyInitError
    _Py_InitializeCore_impl(PyInterpreterState **interp_p,
                            const _PyCoreConfig *core_config)
    {
        PyInterpreterState *interp;
        _PyInitError err;
    	
    	
        //申请创建一个PyDictObject
        PyObject *modules = PyDict_New();
        if (modules == NULL)
            return _Py_INIT_ERR("can't make modules dictionary");
        interp->modules = modules;
        PyObject *bimod = _PyBuiltin_Init();
    }
    

    在调用_PyBuiltin_Init之前,我们看到了interp->modules指向了modules,就是说这个域将维护系统所有的modules。而interp是一个PyInterpreterState对象,因为我们知道操作系统的进程维护这线程使用的资源,而且这些资源是共享的,所以这是很好理解的,不可能说一个线程维护这一个modules,否则就意味着每创建一个线程就要创建一份__builtins__,那这显然是不合理的。我们可以用python实际演示一下:

    import threading
    import builtins
    
    
    def foo1():
        builtins.list, builtins.tuple = builtins.tuple, builtins.list
    
    
    def foo2():
        print(f"猜猜下面代码会输出什么:")
        print("list:", list([1, 2, 3, 4, 5]))
        print("tuple:", tuple([1, 2, 3, 4, 5]))
    
    
    f1 = threading.Thread(target=foo1)
    f1.start()
    f1.join()
    threading.Thread(target=foo2).start()
    """
    猜猜下面代码会输出什么:
    list: (1, 2, 3, 4, 5)
    tuple: [1, 2, 3, 4, 5]
    """
    

    不管是dir、hasattr等函数,list、int、dict等内建对象都是在__builtins__里面,我们通过builtins模块可以直接获取,这算是python给我们提供的一个接口。而我们在foo1中把list和tuple互相对调了,而这么做显然影响到了foo2函数。而foo2是后执行的,但是被影响了,也说明了__builtins__这个内置的命名空间是属于进程级别的,多个线程是共享的。另外这个interp->modules在python一级指的就是sys.modules

    //Python/builtinmodule.c
    PyObject *
    _PyBuiltin_Init(void)
    {
        PyObject *mod, *dict, *debug;
    
        if (PyType_Ready(&PyFilter_Type) < 0 ||
            PyType_Ready(&PyMap_Type) < 0 ||
            PyType_Ready(&PyZip_Type) < 0)
            return NULL;
    	
        //创建并设置__builtins__ module
        mod = _PyModule_CreateInitialized(&builtinsmodule, PYTHON_API_VERSION);
        if (mod == NULL)
            return NULL;
        //将所有python内建对象加入到__builtins__ module中
        dict = PyModule_GetDict(mod);
        ...
        ...
        //老铁们,下面这些东西应该不陌生吧    
        SETBUILTIN("None",                  Py_None);
        SETBUILTIN("Ellipsis",              Py_Ellipsis);
        SETBUILTIN("NotImplemented",        Py_NotImplemented);
        SETBUILTIN("False",                 Py_False);
        SETBUILTIN("True",                  Py_True);
        SETBUILTIN("bool",                  &PyBool_Type);
        SETBUILTIN("memoryview",        &PyMemoryView_Type);
        SETBUILTIN("bytearray",             &PyByteArray_Type);
        SETBUILTIN("bytes",                 &PyBytes_Type);
        SETBUILTIN("classmethod",           &PyClassMethod_Type);
        SETBUILTIN("complex",               &PyComplex_Type);
        SETBUILTIN("dict",                  &PyDict_Type);
        SETBUILTIN("enumerate",             &PyEnum_Type);
        SETBUILTIN("filter",                &PyFilter_Type);
        SETBUILTIN("float",                 &PyFloat_Type);
        SETBUILTIN("frozenset",             &PyFrozenSet_Type);
        SETBUILTIN("property",              &PyProperty_Type);
        SETBUILTIN("int",                   &PyLong_Type);
        SETBUILTIN("list",                  &PyList_Type);
        SETBUILTIN("map",                   &PyMap_Type);
        SETBUILTIN("object",                &PyBaseObject_Type);
        SETBUILTIN("range",                 &PyRange_Type);
        SETBUILTIN("reversed",              &PyReversed_Type);
        SETBUILTIN("set",                   &PySet_Type);
        SETBUILTIN("slice",                 &PySlice_Type);
        SETBUILTIN("staticmethod",          &PyStaticMethod_Type);
        SETBUILTIN("str",                   &PyUnicode_Type);
        SETBUILTIN("super",                 &PySuper_Type);
        SETBUILTIN("tuple",                 &PyTuple_Type);
        SETBUILTIN("type",                  &PyType_Type);
        SETBUILTIN("zip",                   &PyZip_Type);
        debug = PyBool_FromLong(Py_OptimizeFlag == 0);
        if (PyDict_SetItemString(dict, "__debug__", debug) < 0) {
            Py_DECREF(debug);
            return NULL;
        }
        Py_DECREF(debug);
    
        return mod;
    #undef ADD_TO_ALL
    #undef SETBUILTIN
    }
    

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

    • 创建PyModuleObject对象,在python中,module正是通过这个对象来实现的,我们后续章节介绍模块的时候会重点说。
    • 设置module,将python中所有的内建对象都塞到__builtins__中。

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

    PyObject *
    _PyModule_CreateInitialized(struct PyModuleDef* module, int module_api_version)
    {
        const char* name;
        PyModuleObject *m;
    	
        ...
        //拿到module的name,对于当前来说,这里显然是__builtins__
        name = module->m_name;
        
        //这里比较有意思,这是检测模块版本的,针对是需要导入的py文件。
        //我们说编译成字节码之后,会直接从当前目录的__pycache__里面导入
        //而那里面都是pyc文件,介绍字节码的时候我们说,pyc文件的文件名是有python解释器的版本号的
        //这里就是比较版本是否一致,不一致则不导入字节码,而是会重新编译py文件
        if (!check_api_version(name, module_api_version)) {
            return NULL;
        }
        ...
        //创建一个PyModuleObject  
        if ((m = (PyModuleObject*)PyModule_New(name)) == NULL)
            return NULL;
    
        if (module->m_size > 0) {
            m->md_state = PyMem_MALLOC(module->m_size);
            if (!m->md_state) {
                PyErr_NoMemory();
                Py_DECREF(m);
                return NULL;
            }
            memset(m->md_state, 0, module->m_size);
        }
    
        if (module->m_methods != NULL) {
            //遍历methods中指定的module对象中应包含的操作集合
            if (PyModule_AddFunctions((PyObject *) m, module->m_methods) != 0) {
                Py_DECREF(m);
                return NULL;
            }
        }
        if (module->m_doc != NULL) {
            //设置docstring
            if (PyModule_SetDocString((PyObject *) m, module->m_doc) != 0) {
                Py_DECREF(m);
                return NULL;
            }
        }
        m->md_def = module;
        return (PyObject*)m;
    }
    

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

    name:module的名称,在这里就是__builtins__

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

    PyModule_New:用于创建一个PyModuleObject对象

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

    PyModule_AddFunctions:设置methods中的函数操作

    PyModule_SetDocString:设置docstring

    创建module对象

    我们说python中的module在底层cpython中就是一个PyModuleObject对象,我们来看看这个对象长什么样子吧。

    //moduleobject.c
    typedef struct {
        PyObject_HEAD
        PyObject *md_dict;
        struct PyModuleDef *md_def;
        void *md_state;
        PyObject *md_weaklist;
        PyObject *md_name;  /* for logging purposes after md_dict is cleared */
    } PyModuleObject;
    

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

    //moduleobject.c
    PyObject *
    PyModule_New(const char *name)
    {	
        //module name,和PyModuleObject
        PyObject *nameobj, *module;
        nameobj = PyUnicode_FromString(name);
        if (nameobj == NULL)
            return NULL;
        //传入module name,拿到PyModuleObject
        module = PyModule_NewObject(nameobj);
        Py_DECREF(nameobj);
        return module;
    }
    
    PyObject *
    PyModule_NewObject(PyObject *name)
    {	
        //创建一个module对象
        PyModuleObject *m;
        //申请空间
        m = PyObject_GC_New(PyModuleObject, &PyModule_Type);
        if (m == NULL)
            return NULL;
        //设置属性
        m->md_def = NULL;
        m->md_state = NULL;
        m->md_weaklist = NULL;
        m->md_name = NULL;
        //属性字典
        m->md_dict = PyDict_New();
        //这里调用了module_init_dict
        if (module_init_dict(m, m->md_dict, name, NULL) != 0)
            goto fail;
        PyObject_GC_Track(m);
        return (PyObject *)m;
    
     fail:
        Py_DECREF(m);
        return NULL;
    }
    
    static int
    module_init_dict(PyModuleObject *mod, PyObject *md_dict,
                     PyObject *name, PyObject *doc)
    {
        _Py_IDENTIFIER(__name__);
        _Py_IDENTIFIER(__doc__);
        _Py_IDENTIFIER(__package__);
        _Py_IDENTIFIER(__loader__);
        _Py_IDENTIFIER(__spec__);
    
        if (md_dict == NULL)
            return -1;
        if (doc == NULL)
            doc = Py_None;
    	
        //设置name和doc
        if (_PyDict_SetItemId(md_dict, &PyId___name__, name) != 0)
            return -1;
        if (_PyDict_SetItemId(md_dict, &PyId___doc__, doc) != 0)
            return -1;
        if (_PyDict_SetItemId(md_dict, &PyId___package__, Py_None) != 0)
            return -1;
        if (_PyDict_SetItemId(md_dict, &PyId___loader__, Py_None) != 0)
            return -1;
        if (_PyDict_SetItemId(md_dict, &PyId___spec__, Py_None) != 0)
            return -1;
        if (PyUnicode_CheckExact(name)) {
            Py_INCREF(name);
            Py_XSETREF(mod->md_name, name);
        }
    
        return 0;
    }
    
    

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

    设置module对象

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

    static PyMethodDef builtin_methods[] = {
        {"__build_class__", (PyCFunction)builtin___build_class__,
         METH_FASTCALL | METH_KEYWORDS, build_class_doc},
        {"__import__",      (PyCFunction)builtin___import__, METH_VARARGS | METH_KEYWORDS, import_doc},
        BUILTIN_ABS_METHODDEF
        BUILTIN_ALL_METHODDEF
        BUILTIN_ANY_METHODDEF
        BUILTIN_ASCII_METHODDEF
        BUILTIN_BIN_METHODDEF
        {"breakpoint",      (PyCFunction)builtin_breakpoint, METH_FASTCALL | METH_KEYWORDS, breakpoint_doc},
        BUILTIN_CALLABLE_METHODDEF
        BUILTIN_CHR_METHODDEF
        BUILTIN_COMPILE_METHODDEF
        BUILTIN_DELATTR_METHODDEF
        {"dir",             builtin_dir,        METH_VARARGS, dir_doc},
        BUILTIN_DIVMOD_METHODDEF
        BUILTIN_EVAL_METHODDEF
        BUILTIN_EXEC_METHODDEF
        BUILTIN_FORMAT_METHODDEF
        {"getattr",         (PyCFunction)builtin_getattr, METH_FASTCALL, getattr_doc},
        BUILTIN_GLOBALS_METHODDEF
        BUILTIN_HASATTR_METHODDEF
        BUILTIN_HASH_METHODDEF
        BUILTIN_HEX_METHODDEF
        BUILTIN_ID_METHODDEF
        BUILTIN_INPUT_METHODDEF
        BUILTIN_ISINSTANCE_METHODDEF
        BUILTIN_ISSUBCLASS_METHODDEF
        {"iter",            builtin_iter,       METH_VARARGS, iter_doc},
        BUILTIN_LEN_METHODDEF
        BUILTIN_LOCALS_METHODDEF
        {"max",             (PyCFunction)builtin_max,        METH_VARARGS | METH_KEYWORDS, max_doc},
        {"min",             (PyCFunction)builtin_min,        METH_VARARGS | METH_KEYWORDS, min_doc},
        {"next",            (PyCFunction)builtin_next,       METH_FASTCALL, next_doc},
        BUILTIN_OCT_METHODDEF
        BUILTIN_ORD_METHODDEF
        BUILTIN_POW_METHODDEF
        {"print",           (PyCFunction)builtin_print,      METH_FASTCALL | METH_KEYWORDS, print_doc},
        BUILTIN_REPR_METHODDEF
        BUILTIN_ROUND_METHODDEF
        BUILTIN_SETATTR_METHODDEF
        BUILTIN_SORTED_METHODDEF
        BUILTIN_SUM_METHODDEF
        {"vars",            builtin_vars,       METH_VARARGS, vars_doc},
        {NULL,              NULL},
    };
    

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

    总结一下就是:PyInterpreterState_New创建PyInterpreterState,通过传递PyInterpreterState调用PyThreadState_New得到PyThreadState对象。接着就是我们熟悉的内建对象初始化,然后在_Py_InitializeCore_impl中调用_PyBuiltin_Init设置内建属性,中间调用了_PyModule_CreateInitialized,在_PyModule_CreateInitialized里面,先是使用PyModule_New创建一个PyModuleObject,然后在里面设置了name和doc,然后使用PyModule_AddFunctions设置methods,在这里面我们看到一大部分我们熟悉的内置属性,当设置完之后,回到_PyBuiltin_Init,然后再设置一部分属性。至此,__builtins__就完成了。

    另外builtin_methods里面是一个个的PyMethodDef_PyModule_CreateInitialized都会基于它创建一个PyCFunctionObject对象,这个对象是python中对函数指针的包装,当然还将这个函数指针和其它信息联系在了一起。

    //methodobject.h
    typedef struct {
        PyObject_HEAD
        PyMethodDef *m_ml; /* Description of the C function to call */
        PyObject    *m_self; /* Passed as 'self' arg to the C func, can be NULL */
        PyObject    *m_module; /* The __module__ attribute, can be anything */
        PyObject    *m_weakreflist; /* List of weak references */
    } PyCFunctionObject;
    
    //methodobject.c
    PyObject *
    PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)
    {
        PyCFunctionObject *op;
        op = free_list;
        if (op != NULL) {
            free_list = (PyCFunctionObject *)(op->m_self);
            (void)PyObject_INIT(op, &PyCFunction_Type);
            numfree--;
        }
        else {
            op = PyObject_GC_New(PyCFunctionObject, &PyCFunction_Type);
            if (op == NULL)
                return NULL;
        }
        op->m_weakreflist = NULL;
        op->m_ml = ml;
        Py_XINCREF(self);
        op->m_self = self;
        Py_XINCREF(module);
        op->m_module = module;
        _PyObject_GC_TRACK(op);
        return (PyObject *)op;
    }
    
    

    很显然,python对PyCFunctionObject对象采取了缓冲池的策略,不过有了对PyLongObject和PyListObject对象的剖析,我们已经可以将策略猜出个八九不离十了。PyCFunctionObject中的那个m_self,就是module本身。而m_module存放的则是提个PyUnicodeObject对象,对应这个PyModuleObject的名字。

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

    //moduleobject.c
    PyObject *
    PyModule_GetDict(PyObject *m)
    {
        PyObject *d;
        if (!PyModule_Check(m)) {
            PyErr_BadInternalCall();
            return NULL;
        }
        d = ((PyModuleObject *)m) -> md_dict;
        assert(d != NULL);
        return d;
    }
    
    //pylifecycle.c
    _PyInitError
    _Py_InitializeCore_impl(PyInterpreterState **interp_p,
                            const _PyCoreConfig *core_config)
    {
        ...
        PyObject *bimod = _PyBuiltin_Init();
        if (bimod == NULL)
            return _Py_INIT_ERR("can't initialize builtins modules");
        _PyImport_FixupBuiltin(bimod, "builtins", modules);
        interp->builtins = PyModule_GetDict(bimod);
    	...    
    }
    
    

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

    14.2.2 创建sys module

    14.2.2.1 sys module的备份

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

    //pylifecycle.c
    _PyInitError
    _Py_InitializeCore_impl(PyInterpreterState **interp_p,
                            const _PyCoreConfig *core_config)
    {
        
        PyObject *sysmod;
        err = _PySys_BeginInit(&sysmod);
        Py_INCREF(interp->sysdict);
        PyDict_SetItemString(interp->sysdict, "modules", modules); //设置,不需要多说
        _PyImport_FixupBuiltin(sysmod, "sys", modules);//这个是用来备份的,为什么要有这一步呢?后面说
    }
    
    

    _PySys_BeginInit函数主要是在sys模块中加入python的基本信息、如python版本、api版本等等。但是为什么要进行备份呢,首先interp->sysdict和interp->sysdict指向的都是一个PyDictObject,而非PyModuleObject。由于python的module集合interp->modules是一个PyDictObject对象,而PyDictObject在python中是一个可变对象,所以其中维护的(module name, PyModuleObject)有可能在运行时被删除。对于python扩展的module,比如我们import numpy,为了避免多次引用,python会将所有扩展的module通过一个全局的PyDIctObject对象来进行备份维护。这个备份的动作就是通过上面源码中的_PyImport_FixupBuiltin完成的

    //import.c
    int
    _PyImport_FixupBuiltin(PyObject *mod, const char *name, PyObject *modules)
    {
        int res;
        PyObject *nameobj;
        nameobj = PyUnicode_InternFromString(name);
        if (nameobj == NULL)
            return -1;
        res = _PyImport_FixupExtensionObject(mod, nameobj, nameobj, modules);
        Py_DECREF(nameobj);
        return res;
    }
    
    static PyObject *extensions = NULL;
    int
    _PyImport_FixupExtensionObject(PyObject *mod, PyObject *name,
                                     PyObject *filename, PyObject *modules)
    {
        PyObject *dict, *key;
        struct PyModuleDef *def;
        int res;
        //这个extensions是个全局变量
        //如果为NULL,那么会创建一个PyDictObject对象
        if (extensions == NULL) {
            extensions = PyDict_New();
            if (extensions == NULL)
                return -1;
        }
    	
        //获取mod中的md_def域,这是个PyMethodDef,而这里的mod就是sys module
        def = PyModule_GetDef(mod);
    	
        //设置name和mod的对应关系
        if (PyObject_SetItem(modules, name, mod) < 0)
            return -1;
        if (def->m_size == -1) {
            if (def->m_base.m_copy) {
                /* Somebody already imported the module,
                   likely under a different name.
                   XXX this should really not happen. */
                Py_CLEAR(def->m_base.m_copy);
            }
            dict = PyModule_GetDict(mod); //拿到sys module
            if (dict == NULL)
                return -1;
            def->m_base.m_copy = PyDict_Copy(dict);//将sys module这个PyDictObject进行拷贝
            if (def->m_base.m_copy == NULL)
                return -1;
        }
        key = PyTuple_Pack(2, filename, name);
        if (key == NULL)
            return -1;
        //将存有拷贝的新的dict的PyMethodDef存储在extensions中
        res = PyDict_SetItem(extensions, key, (PyObject *)def);
        return 0;
    }
    

    我们看到python内部维护了一个全局变量extensions,这个变量在第一次调用_PyImport_FixupExtensionObject时,会被创建为一个PyDictObject,而且这个PyDictObject对象就是所有已经被python加载的module组成的dict的一个备份。当python系统的module集合中的某个标准扩展module被删除后不久又被重新加载时,python就不需要重新初始化这些module,只需要使用extensions这个用来备份的PyDictObject,将里面的module拿出来即可。因为这就意味着python中的标准扩展module是不会在运行时动态改变的,而事实也是如此。

    所以我们看到,sys本身是一个module(我们说 sys module指的是sys这个模块),但是sys.modules是一个字典,里面是module name和PyModuleObject组合起来的多个entry,当然 sys也在里面。而extensions则是用于备份的,一旦删除再次导入的时候,直接从extensions里面获取,然后加载到sys.module里面即可。

    14.2.2.2 设置module

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

    //pylifecycle.c
    _PyInitError
    _Py_InitializeMainInterpreter(PyInterpreterState *interp,
                                  const _PyMainInterpreterConfig *config)
    {
        ...
        err = add_main_module(interp);
        ...	
    }
    
    //sysmodule.c
    void
    PySys_SetPath(const wchar_t *path)
    {
        PyObject *v;
        if ((v = makepathobject(path, DELIM)) == NULL)
            Py_FatalError("can't create sys.path");
        if (_PySys_SetObjectId(&PyId_path, v) != 0)
            Py_FatalError("can't assign sys.path");
        Py_DECREF(v);
    }
    
    static PyObject *
    makepathobject(const wchar_t *path, wchar_t delim)
    {
        int i, n;
        const wchar_t *p;
        PyObject *v, *w;
    
        n = 1;
        p = path;
        while ((p = wcschr(p, delim)) != NULL) {
            n++;
            p++;
        }
        v = PyList_New(n);
        if (v == NULL)
            return NULL;
        for (i = 0; ; i++) {
            p = wcschr(path, delim);
            if (p == NULL)
                p = path + wcslen(path); /* End of string */
            w = PyUnicode_FromWideChar(path, (Py_ssize_t)(p - path));
            if (w == NULL) {
                Py_DECREF(v);
                return NULL;
            }
            PyList_SET_ITEM(v, i, w);
            if (*p == '')
                break;
            path = p+1;
        }
        return v;
    }
    
    int
    _PySys_SetObjectId(_Py_Identifier *key, PyObject *v)
    {
        PyThreadState *tstate = PyThreadState_GET();
        PyObject *sd = tstate->interp->sysdict;
        if (v == NULL) {
            if (_PyDict_GetItemId(sd, key) == NULL)
                return 0;
            else
                return _PyDict_DelItemId(sd, key);
        }
        else
            return _PyDict_SetItemId(sd, key, v);
    }
    

    最终Python会创建一个PyListObject对象,这个list中包含了一组PyUnicodeObject,每一个PyUnicodeObject的内容就代表了一个搜索路径,就是python中的sys.path。而这个代表路径搜索集合的PyListObject对象也会被设置到sys module里面

    python随后还会进行一些琐碎的动作,包括初始化python的import环境,初始化内建异常。初始化内建异常就是调用PyType_Ready初始化各个异常类,而初始化import环境会在下一章剖析,这里不再详述

    14.2.3 创建_main_ module

    python中存在一个非常特殊的module,也就是__main__。这个__main__估计不用我多说了,我们之前说在PyModule_New中,创建一个module之后,会在这个module对应的PyModuleObject对象的PyDictObject对象md_dict中插入一个名为__name__的key,而value就是__main__,都是PyUnicodeObject。但是对于当前模块来说,这个模块也可以叫做__main__

    a = "我是谁?我在哪?我在干什么?"
    from __main__ import a as hahaha
    print(hahaha)  # 我是谁?我在哪?我在干什么?
    

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

    //pylifecycle.c
    static _PyInitError
    add_main_module(PyInterpreterState *interp)
    {
        PyObject *m, *d, *loader, *ann_dict;
        //创建__main__ module,并将其插入到interp->modules中
        m = PyImport_AddModule("__main__");
        if (m == NULL)
            return _Py_INIT_ERR("can't create __main__ module");
    	
        //获取__main__ module的dict
        //我们说sys.modules里面是{"模块名": "模块"....},但是模块(PyModuleObject)也是维护了个字典
        d = PyModule_GetDict(m);
    	
        //获取interp->modules中的__builtins__ module
        if (PyDict_GetItemString(d, "__builtins__") == NULL) {
            PyObject *bimod = PyImport_ImportModule("builtins");
            if (bimod == NULL) {
                return _Py_INIT_ERR("Failed to retrieve builtins module");
            }
            //将("__builtins__", __builtins__)插入到__main_ module的dict中
            if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {
                return _Py_INIT_ERR("Failed to initialize __main__.__builtins__");
            }
            Py_DECREF(bimod);
        }
    }
    

    因此我们算是知道了,为什么python xxx.py执行的时候,__name____main__了,因为我们这里设置了,而python沿着命名空间寻找的时候,最终会在__main__中发现__name__,且值为__main__。但如果是以import的方式加载的,那么__name__则不是__main__,而是模块名,这就意味着在找到__main__之前,就已经在某个命名空间找到__name__了。但这就奇怪了,无缘无故的,怎么会找到__main__呢?别着急后面我们会揭晓。

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

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

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

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

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

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

    我们发现直接就崩溃了,吓得我赶紧把文件还原了。

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

    //pylifecycle.c
    _PyInitError
    _Py_InitializeMainInterpreter(PyInterpreterState *interp,
                                  const _PyMainInterpreterConfig *config)
    {
        if (!Py_NoSiteFlag) {
            err = initsite(); /* Module site */
    }
    
    static _PyInitError
    initsite(void)
    {
        PyObject *m;
        m = PyImport_ImportModule("site");
        if (m == NULL) {
            //这个报错信息,和我们上面图中是不是一样的呢?
            return _Py_INIT_USER_ERR("Failed to import the site module");
        }
        Py_DECREF(m);
        return _Py_INIT_OK();
    }
    

    在initsite中,只调用了PyImport_ImportModule函数,这个函数是python中import机制的核心所在,将在下一章剖析。我们只需要知道PyImport_ImportModule("numpy")等价于python中的import numpy即可

    14.3 激活python虚拟机

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

    python在Py_Initialize(这个我们好像没有说,其是就是最先调用的函数,一层一层调用到我们说的那个,只不过我们直接跳到中间的重点函数去了)完成之后,最终会通过pymain_run_file调用PyRun_AnyFileExFlags

    //pylifecycle.c
    void
    Py_Initialize(void)
    {
        Py_InitializeEx(1);
    }
    
    void
    Py_InitializeEx(int install_sigs)
    {	
        ...
        err = _Py_InitializeFromConfig(&config);
        ...
    }
    
    _PyInitError
    _Py_InitializeFromConfig(const _PyCoreConfig *config)
    {	
        ...
        err = _Py_InitializeCore(&interp, config);
        ...
    }
    
    //Modules/main.c
    static int
    pymain_run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
    {
        PyObject *unicode, *bytes = NULL;
        const char *filename_str;
        int run;
    	
        //如果有文件名,那么获取文件名
        if (filename) {
            unicode = PyUnicode_FromWideChar(filename, wcslen(filename));
            ...
        }
        //否则执行传入一个<stdin>,显然就是标准输入
        else {
            filename_str = "<stdin>";
        }
    	
        //调用PyRun_AnyFileExFlags
        run = PyRun_AnyFileExFlags(fp, filename_str, filename != NULL, p_cf);
        Py_XDECREF(bytes);
        return run != 0;
    }
    
    
    //pythonrun.c
    int
    PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                         PyCompilerFlags *flags)
    {	
        if (filename == NULL)
            filename = "???";
        //根据fp是否代表交互环境,对程序进行流程控制
        if (Py_FdIsInteractive(fp, filename)) {
            //如果是交互环境,那么调用PyRun_InteractiveLoopFlags
            int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
            if (closeit)
                fclose(fp);
            return err;
        }
        else
            //否则说明是一个普通的python脚本,执行PyRun_SimpleFileExFlags
            return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);
    }
    

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

    14.3.1 交互式运行

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

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

    //pythonrun.c
    int
    PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
    {
        PyObject *filename, *v;
        int ret, err;
        PyCompilerFlags local_flags;
        filename = PyUnicode_DecodeFSDefault(filename_str);
    	
        //创建交互式提示符 
        v = _PySys_GetObjectId(&PyId_ps1);
        if (v == NULL) {
            //如果未指定,那么就是`>>> `
            _PySys_SetObjectId(&PyId_ps1, v = PyUnicode_FromString(">>> "));
            Py_XDECREF(v);
        }
        //同理这个也是一样
        v = _PySys_GetObjectId(&PyId_ps2);
        if (v == NULL) {
            _PySys_SetObjectId(&PyId_ps2, v = PyUnicode_FromString("... "));
            Py_XDECREF(v);
        }
        err = 0;
        do {
            //这里就进入了交互式环境,我们看到每次都调用了PyRun_InteractiveOneObjectEx
            //直到下面的ret != E_EOF不成立 停止循环,一般情况就是我们输入exit()图此处了
            ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
            ...
        } while (ret != E_EOF);
        Py_DECREF(filename);
        return err;
    }
    
    static int
    PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
                                 PyCompilerFlags *flags)
    {
        PyObject *m, *d, *v, *w, *oenc = NULL, *mod_name;
        mod_ty mod;
        PyArena *arena;
        const char *ps1 = "", *ps2 = "", *enc = NULL;
        int errcode = 0;
        _Py_IDENTIFIER(encoding);
        _Py_IDENTIFIER(__main__);
    
        if (fp == stdin) {
           ...
        }
        v = _PySys_GetObjectId(&PyId_ps1);
        if (v != NULL) {
           ...
        }
        w = _PySys_GetObjectId(&PyId_ps2);
        if (w != NULL) {
           ...
        }
        //编译用户在交互式环境下输入的python语句
        arena = PyArena_New();
        if (arena == NULL) {
            Py_XDECREF(v);
            Py_XDECREF(w);
            Py_XDECREF(oenc);
            return -1;
        }
        //生成抽象语法树
        mod = PyParser_ASTFromFileObject(fp, filename, enc,
                                         Py_single_input, ps1, ps2,
                                         flags, &errcode, arena);
        ...
        //获取<module __main__>中维护的dict
        m = PyImport_AddModuleObject(mod_name);
        if (m == NULL) {
            PyArena_Free(arena);
            return -1;
        }
        d = PyModule_GetDict(m);
        //执行用户输入的python语句
        v = run_mod(mod, filename, d, d, flags, arena);
        PyArena_Free(arena);
        if (v == NULL) {
            return -1;
        }
        Py_DECREF(v);
        flush_io();
        return 0;
    }
    
    

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

    14.3.2 脚本文件运行方式

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

    //compile.h
    #define Py_file_input 257
    
    //pythonrun.c
    int
    PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                            PyCompilerFlags *flags)
    {
        PyObject *m, *d, *v;
        const char *ext;
        int set_file_name = 0, ret = -1;
        size_t len;
    	
        //__main__就是当前文件
        m = PyImport_AddModule("__main__");
        if (m == NULL)
            return -1;
        Py_INCREF(m);
        //还记得这个d吗?当前活动的frame对象的local和global命名空间
        d = PyModule_GetDict(m);
        //在__main__中设置__file__属性
        if (PyDict_GetItemString(d, "__file__") == NULL) {
            PyObject *f;
            f = PyUnicode_DecodeFSDefault(filename);
            if (f == NULL)
                goto done;
            if (PyDict_SetItemString(d, "__file__", f) < 0) {
                Py_DECREF(f);
                goto done;
            }
            if (PyDict_SetItemString(d, "__cached__", Py_None) < 0) {
                Py_DECREF(f);
                goto done;
            }
            set_file_name = 1;
            Py_DECREF(f);
        }
        len = strlen(filename);
        ext = filename + len - (len > 4 ? 4 : 0);
        if (maybe_pyc_file(fp, filename, ext, closeit)) {
            //如果是pyc
            FILE *pyc_fp;
            /* Try to run a pyc file. First, re-open in binary */
            if (closeit)
                fclose(fp);
            //二进制模式打开
            if ((pyc_fp = _Py_fopen(filename, "rb")) == NULL) {
                fprintf(stderr, "python: Can't reopen .pyc file
    ");
                goto done;
            }
            if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) {
                fprintf(stderr, "python: failed to set __main__.__loader__
    ");
                ret = -1;
                fclose(pyc_fp);
                goto done;
            }
            v = run_pyc_file(pyc_fp, filename, d, d, flags);
        } else {
            /* When running from stdin, leave __main__.__loader__ alone */
            if (strcmp(filename, "<stdin>") != 0 &&
                set_main_loader(d, filename, "SourceFileLoader") < 0) {
                fprintf(stderr, "python: failed to set __main__.__loader__
    ");
                ret = -1;
                goto done;
            }
            //执行脚本文件
            v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                                  closeit, flags);
        }
        ...
    }
    
    PyObject *
    PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                      PyObject *locals, int closeit, PyCompilerFlags *flags)
    {
        PyObject *ret = NULL;
        mod_ty mod;
        PyArena *arena = NULL;
        PyObject *filename;
    
        filename = PyUnicode_DecodeFSDefault(filename_str);
        if (filename == NULL)
            goto exit;
    
        arena = PyArena_New();
        if (arena == NULL)
            goto exit;
    	
        //编译
        mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                         flags, NULL, arena);
        if (closeit)
            fclose(fp);
        if (mod == NULL) {
            goto exit;
        }
        //执行
        ret = run_mod(mod, filename, globals, locals, flags, arena);
    
    exit:
        Py_XDECREF(filename);
        if (arena != NULL)
            PyArena_Free(arena);
        return ret;
    }
    
    

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

    14.3.3 启动虚拟机

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

    static PyObject *
    run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
                PyCompilerFlags *flags, PyArena *arena)
    {
        PyCodeObject *co;
        PyObject *v;
        //基于ast编译字节码指令序列,创建PyCodeObject对象
        co = PyAST_CompileObject(mod, filename, flags, -1, arena);
        if (co == NULL)
            return NULL;
        //创建PyFrameObject,执行PyCodeObject对象中的字节码指令序列
        v = PyEval_EvalCode((PyObject*)co, globals, locals);
        Py_DECREF(co);
        return v;
    }
    

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

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

    //ceval.c
    PyObject *
    PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
    {
        return PyEval_EvalCodeEx(co,
                          globals, locals,
                          (PyObject **)NULL, 0,
                          (PyObject **)NULL, 0,
                          (PyObject **)NULL, 0,
                          NULL, NULL);
    }
    
    PyObject *
    PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
                      PyObject *const *args, int argcount,
                      PyObject *const *kws, int kwcount,
                      PyObject *const *defs, int defcount,
                      PyObject *kwdefs, PyObject *closure)
    {
        return _PyEval_EvalCodeWithName(_co, globals, locals,
                                        args, argcount,
                                        kws, kws != NULL ? kws + 1 : NULL,
                                        kwcount, 2,
                                        defs, defcount,
                                        kwdefs, closure,
                                        NULL, NULL);
    }
    
    PyObject *
    _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
               PyObject *const *args, Py_ssize_t argcount,
               PyObject *const *kwnames, PyObject *const *kwargs,
               Py_ssize_t kwcount, int kwstep,
               PyObject *const *defs, Py_ssize_t defcount,
               PyObject *kwdefs, PyObject *closure,
               PyObject *name, PyObject *qualname)
    {
        ...
        retval = PyEval_EvalFrameEx(f,0);
    }
    

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

    14.3.4 命名空间

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

    //frameobject.c
    PyFrameObject*
    PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
                PyObject *globals, PyObject *locals)
    {
        PyFrameObject *f = _PyFrame_New_NoTrack(tstate, code, globals, locals);
        if (f)
            _PyObject_GC_TRACK(f);
        return f;
    }
    
    
    PyFrameObject* _Py_HOT_FUNCTION
    _PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
                         PyObject *globals, PyObject *locals)
    {
        PyFrameObject *back = tstate->frame;
        PyFrameObject *f;
        PyObject *builtins;
        Py_ssize_t i;
    	
        //设置builtin命名空间
        if (back == NULL || back->f_globals != globals) {
            //但是我们发现设置builtins,居然是从globals里面获取的
            //带着这个疑问,看看下面更大的疑问
            builtins = _PyDict_GetItemId(globals, &PyId___builtins__);
            ...
        }
        else {
            /* If we share the globals, we share the builtins.
               Save a lookup and a call. */
            builtins = back->f_builtins;
            assert(builtins != NULL);
            Py_INCREF(builtins);
        }
        ...
        f->f_builtins = builtins;
        ...
    	//设置global命名空间        
        f->f_globals = globals;
    	//设置local命名空间
        if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) ==
            (CO_NEWLOCALS | CO_OPTIMIZED))
            ; /* f_locals = NULL; will be set by PyFrame_FastToLocals() */
        else if (code->co_flags & CO_NEWLOCALS) {
            locals = PyDict_New();
            if (locals == NULL) {
                Py_DECREF(f);
                return NULL;
            }
            f->f_locals = locals;
        }
        else {
            if (locals == NULL)
                locals = globals; //如果locals为NULL,那么等同于globals,显然这是针对模块来的
            Py_INCREF(locals);
            f->f_locals = locals;
        }
    
        f->f_lasti = -1;
        f->f_lineno = code->co_firstlineno;
        f->f_iblock = 0;
        f->f_executing = 0;
        f->f_gen = NULL;
        f->f_trace_opcodes = 0;
        f->f_trace_lines = 1;
    
        return f;
    }
    

    我们在python的层面上,看一个更加奇怪的现象

    # 代表了globals()里面存放了builtins
    print(globals()["__builtins__"])  # <module 'builtins' (built-in)>
    
    # 我们说builtins里面包含了所有的内置对象、函数等等,显然调用int是可以的
    print(globals()["__builtins__"].int("123"))  # 123
    
    # 但是,我居然能从builtins里面拿到globals
    # 不过也很好理解,因为globals是一个内置函数,肯定是在builtins里面
    print(globals()["__builtins__"].globals)  # <built-in function globals>
    
    # 于是拿到了globals,继续调用,然后获取__builtins__,又拿到了builtins,而且我们是可以调用list的
    print(globals()["__builtins__"].globals()["__builtins__"].list("abcd"))  # ['a', 'b', 'c', 'd']
    

    为什么会出现这个情况,看样子我们似乎可以无限下去,不考虑内存、栈溢出等情况。而且从global命名空间获取builtins、从builtins里面获取global,这难道不会冲突吗?然而事实上,我有点偷换概念了,如果仔细想想的就能明白。

    我们从builtins里面获取global命名空间是通过什么形式获取的,通过globals,而这个globals是一个函数啊。同理从源码中我们也看到了,创建builtins,还需要用到globals。

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

    # 我们看到,builtins里面获取的__name__居然不是__main__,而是builtins
    print(globals()["__builtins__"].__name__)  # builtins
    
    # 首先是按照local  global builtins的顺序查找是没问题的
    # 而对于模块来说,我们知道locals为NULL,然后直接把globals赋值给locals了
    # 而local里面有__name__,就是__main__,所以__main__和builtins.__name__不是一个东西
    print(globals()["__name__"])  # __main__
    # 初始化builtins的时候,那个__name__指的是builtins这个PyModuleObject的__name__
    # 而对于我们py文件这个模块来说,__name__是设置在global命名空间里面的
    

  • 相关阅读:
    安装VMware16兼容Hyper-v+WSL2+Docker+解决0x80370102报错
    家用联通光纤开启IPv6
    配置微软Azure大数据HDInsight云集群
    Hadoop集群搭建-05安装配置YARN
    Hadoop集群搭建-04安装配置HDFS
    Hadoop集群搭建-03编译安装hadoop
    Hadoop集群搭建-02安装配置Zookeeper
    Hadoop集群搭建-01前期准备
    springMVC+request.session实现用户登录和访问权限控制
    idea+spring4+springmvc+mybatis+maven实现简单增删改查CRUD
  • 原文地址:https://www.cnblogs.com/traditional/p/12103873.html
Copyright © 2011-2022 走看看