《python解释器源码剖析》第14章--python运行环境初始化

14.0 序

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

14.1 线程环境初始化

14.1.1 线程模型回顾

我们之前介绍栈帧的时候,说过python中的线程模型,python中的一个线程对应C中的一个线程,也对应操作系统中的一个原生线程。当时还强调,python中的线程是对操作系统线程的模拟,记录了操作系统线程的状态。而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 == '\0')
            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\n");
            goto done;
        }
        if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) {
            fprintf(stderr, "python: failed to set __main__.__loader__\n");
            ret = -1;
            fclose(pyc_fp);
            goto done;
        }
        v = run_pyc_file(pyc_fp, filename, d, d, flags);
    } else {
        /* 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__\n");
            ret = -1;
            goto done;
        }
        //执行脚本文件
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }
    ...
}

PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    PyObject *ret = NULL;
    mod_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命名空间里面的

猜你喜欢

转载自www.cnblogs.com/traditional/p/12103873.html