《深度剖析CPython解釋器》24. Python運行時環境的初始化、源碼分析Python解釋器在啟動時都做了哪些事情?


楔子

我們之前分析了Python的核心--字節碼、以及虛擬機的剖析工作,但這僅僅只是一部分,而其余的部分則被遮在了幕后。記得我們在分析虛擬機的時候,曾這么說過:

當Python啟動后,首先會進行 "運行時環境" 的初始化,而關於 "運行時環境" 的初始化是一個非常復雜的過程。並且 "運行時環境" 和 "執行環境" 是不同的, "運行時環境" 是一個全局的概念,而 "執行環境" 是一個棧幀。關於"運行時環境"我們后面將用單獨的一章進行剖析,這里就假設初始化動作已經完成,我們已經站在了Python虛擬機的門檻外面,只需要輕輕推動一下第一張骨牌,整個執行過程就像多米諾骨牌一樣,一環扣一環地展開。

所以這次,我們將回到時間的起點,從Python的應用程序被執行開始,一步一步緊緊跟隨Python的軌跡,完整地展示Python在啟動之初的所有動作。當我們根據Python完成所有的初始化動作之后,也就能對Python執行引擎執行字節碼指令時的整個運行環境了如執掌了。

線程環境初始化

我們知道線程是操作系統調度的最小單元,那么Python中的線程又是怎么樣的呢?

線程模型

我們之前介紹棧幀的時候說過,通過Python啟動一個線程,那么底層會通過C來啟動一個線程,然后啟動操作系統的一個原生線程(OS線程)。所以Python中的線程實際上是對OS線程的一個封裝,因此Python中的線程是貨真價實的。

然后Python還提供了一個PyThreadState(線程狀態)對象,維護OS線程執行的狀態信息,相當於是OS線程的一個抽象描述。雖然真正用來執行的線程及其狀態肯定是由操作系統進行維護的,但是Python虛擬機在運行的時候總需要另外一些與線程相關的狀態和信息,比如是否發生了異常等等,這些信息顯然操作系統是沒有辦法提供的。而PyThreadState對象正是Python為OS線程准備的、在虛擬機層面保存其狀態信息的對象,也就是線程狀態對象。而在Python中,當前活動的OS線程對應的PyThreadState對象可以通過PyThreadState_GET獲得,有了線程狀態對象之后,就可以設置一些額外信息了。具體內容,我們后面會說。

當然除了線程狀態對象之外,還有進程狀態對象,我們來看看兩者在Python底層的定義是什么?它們位於 Include/pystate.h 中。

typedef struct _is PyInterpreterState;
typedef struct _ts PyThreadState;

里面的 PyInterpreterState 表示進程狀態對象, PyThreadState 表示線程狀態對象。但是我們看到它們都是typedef起得一個別名,而定義的結構體 struct  _is 位於 Include/cpython/pystate.h 中, struct  _ts 位於 Include/internal/pycore_pystate.h中。

線程狀態對象:

struct _ts {
    struct _ts *prev;  //多個線程狀態對象也像鏈表一樣串起來, 因為一個進程里面是可以包含多個線程的, prev指向上一個線程狀態對象
    struct _ts *next;  //指向下一個線程狀態對象
    PyInterpreterState *interp;  //進程狀態對象, 標識對應的線程是屬於哪一個進程的

    struct _frame *frame; //棧幀對象, 模擬線程中函數的調用堆棧
    int recursion_depth;  //遞歸深度
    //.....
    //.....
    //....
    uint64_t id; //線程id
};

進程狀態對象:

struct _is {

    struct _is *next; //當前進程的下一個進程
    struct _ts *tstate_head; //進程環境中的線程狀態對象的集合, 我們說線程狀態對象會形成一個鏈表, 這里就是鏈表的頭結點

    int64_t id; //線程id
 	//....
    PyObject *audit_hooks;
};

我們說 PyInterpreterState 對象是對進程的模擬, PyThreadState 是對線程的模擬。我們之前分析虛擬機的時候說過其執行環境,如果再將運行時環境加進去的話。

線程環境的初始化

在Python啟動之后,初始化的動作是從 Py_NewInterpreter 函數開始的,然后這個函數調用了 new_interpreter 函數完成初始化,我們分析會先從 new_interpreter 函數開始,當然 Py_NewInterpreter 里面也做了一些工作,具體的后面會說。

我們知道在Windows平台上,當執行一個可執行文件時,操作系統首先創建一個進程內核。同理在Python中亦是如此,會在 new_interpreter 中調用 PyInterpreterState_New 創建一個嶄新的 PyInterpreterState對象。該函數位於 Python/pystate.c 中。

PyInterpreterState *
PyInterpreterState_New(void)
{
    //申請進程狀態對象所需要的內存
    PyInterpreterState *interp = PyMem_RawMalloc(sizeof(PyInterpreterState));
    if (interp == NULL) {
        return NULL;
    }
	
    //設置屬性
    //......
    //......
    return interp;
}

關於進程狀態對象我們不做過多解釋,只需要知道Python解釋器在啟動時,會創建一個、或者多個 PyInterpreterState 對象,然后通過內部的next指針將多個 PyInterpreterState 串成一個鏈表結構。

在調用 PyInterpreterState_New 成功創建 PyInterpreterState之后,會再接再厲,調用 PyThreadState_New 創建一個全新的線程狀態對象,相關函數定義同樣位於 Python/pystate.c 中。

PyThreadState *
PyThreadState_New(PyInterpreterState *interp)
{
    //我們注意到這個函數接收一個PyInterpreterState
    //這些說明了線程是依賴於進程的,因為需要進程分配資源,而且這個函數又調用了new_threadstate
    //除了傳遞PyInterpreterState之外,還傳了一個1,想也不用想肯定是創建的線程數量
    //這里創建1個,也就是主線程(main thread)
    return new_threadstate(interp, 1);
}


static PyThreadState *
new_threadstate(PyInterpreterState *interp, int init)
{	
    _PyRuntimeState *runtime = &_PyRuntime;
    //為線程狀態對象申請內存
    PyThreadState *tstate = (PyThreadState *)PyMem_RawMalloc(sizeof(PyThreadState));
    if (tstate == NULL) {
        return NULL;
    }
	//設置從線程中獲取函數調用棧的操作
    if (_PyThreadState_GetFrame == NULL) {
        _PyThreadState_GetFrame = threadstate_getframe;
    }
	
    //設置該線程所在的進程
    tstate->interp = interp;
	
    //下面就是設置內部的成員屬性
    tstate->frame = NULL;  //棧幀
    tstate->recursion_depth = 0; //遞歸深度
    tstate->id = ++interp->tstate_next_unique_id;//線程id
    //......
    //......
    //......
    tstate->prev = NULL; //上一個線程狀態對象
    tstate->next = interp->tstate_head;//當前線程狀態對象的next, 我們看到指向了線程狀態對象鏈表的頭結點, 說明是頭插法
    if (tstate->next)
        //因為每個線程狀態對象的prev指針都要指向它的上一個線程狀態對象, 如果是頭結點的話, 那么prev就指向NULL
        //但由於新的線程狀態對象在插入之后顯然就變成了鏈表的頭結點, 因此還需要將插入之間的頭結點的prev指向新插入的線程狀態對象
        tstate->next->prev = tstate;
    //將tstate_head設置為新的線程狀態對象(鏈表的頭結點)
    interp->tstate_head = tstate;

    //返回線程狀態對象
    return tstate;
}

PyInterpreterState_New 相同,  PyThreadState_New 申請內存,創建 PyThreadState 對象,並且對其中每個成員進行初始化。而且其中的prev指針和next指針分別指向了上一個線程狀態對象和下一個線程狀態對象。而且也肯定會存在某一時刻,存在多個 PyThreadState 對象形成一個鏈表,那么什么時刻會發生這種情況呢?顯然用鼻子想也知道這是在Python啟動多線程(下一章分析)的時候。

此外我們看到Python在插入線程狀態對象的時候采用的是頭插法。

我們說Python設置了從線程中獲取函數調用棧的操作,所謂函數調用棧就是我們前面章節說的PyFrameObject對象鏈表。而且在源碼中,我們看到了 PyThreadState 關聯了 PyInterpreterStatePyInterpreterState 也關聯了 PyInterpreterState 。到目前為止,僅有的兩個對象建立起了聯系。對應到Windows,或者說操作系統,我們說進程和線程建立了聯系

PyInterpreterStatePyThreadState 建立了聯系之后,那么就很容易在 PyInterpreterStatePyThreadState 之間穿梭。並且在Python運行時環境中,會有一個變量(先買個關子)一直維護着當前活動的線程,更准確的說是當前活動線程(OS線程)對應的 PyThreadState 對象。初始時,該變量為NULL。在Python啟動之后創建了第一個 PyThreadState 之后,會用該 PyThreadState 對象調用 PyThreadState_Swap 函數來設置這個變量,函數位於 Python/pystate.c 中。

PyThreadState *
PyThreadState_Swap(PyThreadState *newts)
{	
    //調用了_PyThreadState_Swap, 里面傳入了兩個參數, 第一個我們后面說, 顯然從名字上看我們知道這是個GIL相關的
    //第二個參數就是創建的線程狀態對象
    return _PyThreadState_Swap(&_PyRuntime.gilstate, newts);
}


PyThreadState *
_PyThreadState_Swap(struct _gilstate_runtime_state *gilstate, PyThreadState *newts)
{	
    //這里是獲取當前的線程狀態對象, 並且保證線程的安全性
    PyThreadState *oldts = _PyRuntimeGILState_GetThreadState(gilstate);
    //將GIL交給newts
    _PyRuntimeGILState_SetThreadState(gilstate, newts);
    //....
    return oldts;
}

//通過&(gilstate)->tstate_current獲取當前線程
#define _PyRuntimeGILState_GetThreadState(gilstate) \
    ((PyThreadState*)_Py_atomic_load_relaxed(&(gilstate)->tstate_current))
//將newts設置為當前線程, 可以理解為發生了線程的切換
#define _PyRuntimeGILState_SetThreadState(gilstate, value) \
    _Py_atomic_store_relaxed(&(gilstate)->tstate_current, \
                             (uintptr_t)(value))

然后我們看到這兩個宏里面出現了 _Py_atomic_load_relaxed_Py_atomic_store_relaxed&(gilstate)->tstate_current ,這些又是什么呢?還有到底哪個變量在維護這當前的活動線程對應的狀態對象呢?其實那兩個宏已經告訴你了。

//Include/internal/pycore_pystate.h
struct _gilstate_runtime_state {
    //...
    //宏里面出現的gilstate就是該結構體實例, tstate_current指的就是當前活動的OS線程對應的狀態對象
    //同時也是獲取到GIL的Python線程
    _Py_atomic_address tstate_current;
    //...
};


//Include/internal/pycore_atomic.h
#define _Py_atomic_load_relaxed(ATOMIC_VAL) \
    _Py_atomic_load_explicit((ATOMIC_VAL), _Py_memory_order_relaxed)

#define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL) \
    _Py_atomic_store_explicit((ATOMIC_VAL), (NEW_VAL), _Py_memory_order_relaxed)

#define _Py_atomic_load_explicit(ATOMIC_VAL, ORDER) \
    atomic_load_explicit(&((ATOMIC_VAL)->_value), ORDER)

#define _Py_atomic_store_explicit(ATOMIC_VAL, NEW_VAL, ORDER) \
    atomic_store_explicit(&((ATOMIC_VAL)->_value), NEW_VAL, ORDER)
//_Py_atomic_load_relaxed用到了_Py_atomic_load_explicit, _Py_atomic_load_explicit用到了atomic_load_explicit
//_Py_atomic_store_relaxed用到了_Py_atomic_store_explicit, _Py_atomic_store_explicit用到了atomic_store_explicit

//而atomic_load_explicit和atomic_store_explicit是系統頭文件stdatomic.h中定義的api,這是在系統的api中修改的,所以說是線程安全的

介紹完中間部分的內容,那么我們可以從頭開始分析Python運行時的初始化了,我們說它是在 new_interpreter 函數中調用 _PyRuntime_Initialize 函數時開始的,函數位於 Python/pylifecycle.c 中。

PyThreadState *
Py_NewInterpreter(void)
{	
    //線程狀態對象
    PyThreadState *tstate = NULL;
    //傳入線程對象, 調用new_interpreter
    PyStatus status = new_interpreter(&tstate);
    //異常檢測
    if (_PyStatus_EXCEPTION(status)) {
        Py_ExitStatusException(status);
    }
    //返回線程狀態對象, 顯然不會返回一個NULL, 這就說明在new_interpreter中線程狀態對象就已經被設置了
    return tstate;
}

另外里面出現了一個 PyStatus, 表示程序執行的狀態, 會檢測是否發生了異常,該結構體定義在 Include/cpython/initconfig.h 中。

typedef struct {
    enum {
        _PyStatus_TYPE_OK=0,
        _PyStatus_TYPE_ERROR=1,
        _PyStatus_TYPE_EXIT=2
    } _type;
    const char *func;
    const char *err_msg;
    int exitcode;
} PyStatus;

然后我們的重點是 new_interpreter函數,我們進程狀態對象的創建就是在這個函數里面發生的,該函數位於Python/pylifecycle.c中。

static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    PyStatus status; //狀態對象
	
    //運行時初始化, 如果出現異常直接返回
    status = _PyRuntime_Initialize();
    if (_PyStatus_EXCEPTION(status)) {
        return status;
    }
    //......
    // 創建一個進程狀態對象
    PyInterpreterState *interp = PyInterpreterState_New();
    //......
    //根據進程狀態對象創建一個線程狀態對象, 維護對應OS線程的狀態
    PyThreadState *tstate = PyThreadState_New(interp);
    //將GIL的控制權交給創建的線程
    PyThreadState *save_tstate = PyThreadState_Swap(tstate);

    //...
}

Python在初始化運行時環境時,肯定也要對類型系統進行初始化等等,整體是一個非常龐大的過程。有興趣的話,可以追根溯源對着源碼閱讀以下。

到這里,我們對 new_interpreter 算是有了一個階段性的成功,我們創建了代表進程和線程概念的 PyInterpreterStatePyThreadState 對象,並且在它們之間建立的聯系。下面, new_interpreter 將進行入另一個環節,設置系統module。

創建__builtins__

在 new_interpreter 中當Python解釋器創建了 PyInterpreterStatePyThreadState 對象之后,就會開始設置系統的__builtins__了。

static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    //....
    //申請一個PyDictObject對象, 用於存儲所有的module對象
    //而我們說Python中的module對象都是存在sys.modules中的, 所以這里的modules指的就是Python中的sys.modules
    PyObject *modules = PyDict_New();
    if (modules == NULL) {
        return _PyStatus_ERR("can't make modules dictionary");
    }
    
    //然后讓interp -> modules維護modules
    //我們翻看到這個interp表示的時進程實例對象, 這說明什么? 顯然是該進程內的多個線程共享同一個內置名字空間
    interp->modules = modules;
	
    //加載sys模塊, 我們說所有的module對象都在sys.modules中
    PyObject *sysmod = _PyImport_FindBuiltin("sys", modules);
    if (sysmod != NULL) {
        interp->sysdict = PyModule_GetDict(sysmod);
        if (interp->sysdict == NULL) {
            goto handle_error;
        }
        Py_INCREF(interp->sysdict);
        PyDict_SetItemString(interp->sysdict, "modules", modules);
        if (_PySys_InitMain(runtime, interp) < 0) {
            return _PyStatus_ERR("can't finish initializing sys");
        }
    }
	
    //加載內置模塊, builtins是內置模塊, 可以import builtins, 並且builtins.list等價於list
    PyObject *bimod = _PyImport_FindBuiltin("builtins", modules);
    if (bimod != NULL) {
        interp->builtins = PyModule_GetDict(bimod);
        if (interp->builtins == NULL)
            goto handle_error;
        Py_INCREF(interp->builtins);
    }
    //......
}

整體還是比較清晰和直觀的,另外我們說內置名字空間是由進程來維護的,因為進程就是用來為線程提供資源的。但是我們也能看出,這意味着一個進程內的多個線程共享同一個內置作用域,顯然這是非常合理的,不可能每開啟一個線程,就為其創建一個__builtins__。我們來從Python的角度證明這一點:

import threading
import builtins


def foo1():
    builtins.list, builtins.tuple = builtins.tuple, builtins.list


def foo2():
    print(f"猜猜下面代碼會輸出什么:")
    print("list:", list([1, 2, 3, 4, 5]))
    print("tuple:", tuple([1, 2, 3, 4, 5]))


f1 = threading.Thread(target=foo1)
f1.start()
f1.join()
threading.Thread(target=foo2).start()
"""
猜猜下面代碼會輸出什么:
list: (1, 2, 3, 4, 5)
tuple: [1, 2, 3, 4, 5]
"""

我們說所有的內建對象和內置函數都在內置名字空間里面,我們可以通過 import builtins獲取、也可以直接通過__builtins__這個變量來獲取。我們在foo1中把list和tuple互換了,而這個結果顯然也影響到了foo2函數。這也說明了__builtins__是屬於進程級別的,它是被多個線程共享的。所以是interp -> modules = modules,當然這個modules是sys.modules,因為不止內置名字空間,所有的module對象都是被多個線程共享的。

而對__builts__的初始化時在 _PyBuiltin_Init 函數中進行的,它位於 Python/bltinmodule.c 中。

PyObject *
_PyBuiltin_Init(void)
{
    PyObject *mod, *dict, *debug;

    const PyConfig *config = &_PyInterpreterState_GET_UNSAFE()->config;

    if (PyType_Ready(&PyFilter_Type) < 0 ||
        PyType_Ready(&PyMap_Type) < 0 ||
        PyType_Ready(&PyZip_Type) < 0)
        return NULL;
	
    //創建並設置__builtins__ module
    mod = _PyModule_CreateInitialized(&builtinsmodule, PYTHON_API_VERSION);
    if (mod == NULL)
        return NULL;
    //將所有python內建對象加入到__builtins__ module中
    dict = PyModule_GetDict(mod);

    //......
    //老鐵們,下面這些東西應該不陌生吧   
    SETBUILTIN("None",                  Py_None);
    SETBUILTIN("Ellipsis",              Py_Ellipsis);
    SETBUILTIN("NotImplemented",        Py_NotImplemented);
    SETBUILTIN("False",                 Py_False);
    SETBUILTIN("True",                  Py_True);
    SETBUILTIN("bool",                  &PyBool_Type);
    SETBUILTIN("memoryview",        &PyMemoryView_Type);
    SETBUILTIN("bytearray",             &PyByteArray_Type);
    SETBUILTIN("bytes",                 &PyBytes_Type);
    SETBUILTIN("classmethod",           &PyClassMethod_Type);
    SETBUILTIN("complex",               &PyComplex_Type);
    SETBUILTIN("dict",                  &PyDict_Type);
    SETBUILTIN("enumerate",             &PyEnum_Type);
    SETBUILTIN("filter",                &PyFilter_Type);
    SETBUILTIN("float",                 &PyFloat_Type);
    SETBUILTIN("frozenset",             &PyFrozenSet_Type);
    SETBUILTIN("property",              &PyProperty_Type);
    SETBUILTIN("int",                   &PyLong_Type);
    SETBUILTIN("list",                  &PyList_Type);
    SETBUILTIN("map",                   &PyMap_Type);
    SETBUILTIN("object",                &PyBaseObject_Type);
    SETBUILTIN("range",                 &PyRange_Type);
    SETBUILTIN("reversed",              &PyReversed_Type);
    SETBUILTIN("set",                   &PySet_Type);
    SETBUILTIN("slice",                 &PySlice_Type);
    SETBUILTIN("staticmethod",          &PyStaticMethod_Type);
    SETBUILTIN("str",                   &PyUnicode_Type);
    SETBUILTIN("super",                 &PySuper_Type);
    SETBUILTIN("tuple",                 &PyTuple_Type);
    SETBUILTIN("type",                  &PyType_Type);
    SETBUILTIN("zip",                   &PyZip_Type);
    debug = PyBool_FromLong(config->optimization_level == 0);
    if (PyDict_SetItemString(dict, "__debug__", debug) < 0) {
        Py_DECREF(debug);
        return NULL;
    }
    Py_DECREF(debug);

    return mod;
#undef ADD_TO_ALL
#undef SETBUILTIN
}

整個 _PyBuiltin__Init 函數的功能就是設置好__builtins__ module,而這個過程是分為兩步的。

  • 通過_PyModule_CreateInitialized函數創建PyModuleObject對象,我們知道這是Python中模塊對象的底層實現;
  • 設置module,將python中所有的內建對象都塞到__builtins__中

但是我們看到設置的東西似乎少了不少,比如dir、hasattr、setattr等等,這些明顯也是內置的,但是它們到哪里去了。別急,我們剛才說創建__builtins__分為兩步,第一步是創建PyModuleObject,而使用的函數就是 _PyModule_CreateInitialized ,而在這個函數里面就已經完成了大部分設置__builtins__的工作。該函數位於 Object/moduleobject.c 。

PyObject *
_PyModule_CreateInitialized(struct PyModuleDef* module, int module_api_version)
{
    const char* name;
    PyModuleObject *m;
	
    //初始化
    if (!PyModuleDef_Init(module))
        return NULL;
    //拿到module的name,對於當前來說,這里顯然是__builtins__
    name = module->m_name;
    //這里比較有意思,這是檢測模塊版本的,針對的是需要導入的py文件。
    //我們說編譯成PyCodeObject對象之后,會直接從當前目錄的__pycache__里面導入
    //而那里面都是pyc文件,介紹字節碼的時候我們說,pyc文件的文件名是有Python解釋器的版本號的
    //這里就是比較版本是否一致,不一致則不導入pyc文件,而是會重新編譯py文件    
    if (!check_api_version(name, module_api_version)) {
        return NULL;
    }
    if (module->m_slots) {
        PyErr_Format(
            PyExc_SystemError,
            "module %s: PyModule_Create is incompatible with m_slots", name);
        return NULL;
    }
    //創建一個PyModuleObject 
    if ((m = (PyModuleObject*)PyModule_New(name)) == NULL)
        return NULL;

    //.......
    if (module->m_methods != NULL) {
        //遍歷methods中指定的module對象中應包含的操作集合
        if (PyModule_AddFunctions((PyObject *) m, module->m_methods) != 0) {
            Py_DECREF(m);
            return NULL;
        }
    }
    if (module->m_doc != NULL) {
        //設置docstring
        if (PyModule_SetDocString((PyObject *) m, module->m_doc) != 0) {
            Py_DECREF(m);
            return NULL;
        }
    }
    m->md_def = module;
    return (PyObject*)m;
}

根據上面的代碼我們可以得出如下信息:

  • 1. name:module對象的名稱,在這里就是__builtins__
  • 2. module_api_version:python內部使用的version值,用於比較
  • 3. PyModule_New:用於創建一個PyModuleObject對象
  • 4. methods:該module中所包含的函數的集合,在這里是builtin_methods
  • 5. PyModule_AddFunctions:設置methods中的函數操作
  • 6. PyModule_SetDocString:設置docstring

創建module對象

我們說Python中的module對象在底層cpython中對應的結構體是PyModuleObject對象,我們來看看它長什么樣子吧,定義在 Objects/moduleobject.c 中。

typedef struct {
    PyObject_HEAD  //頭部信息
    PyObject *md_dict;  //屬性字典, 所有的屬性和值都在里面
    struct PyModuleDef *md_def;  //module對象包含的操作集合, 里面是一些結構體, 每個結構體包含一個函數的相關信息
    //...
    PyObject *md_name;  //模塊名
} PyModuleObject;

而這個對象我們知道是通過PyModule_New創建的。

PyObject *
PyModule_New(const char *name)
{	
    //module對象的name、PyModuleObject
    PyObject *nameobj, *module;
    nameobj = PyUnicode_FromString(name);
    if (nameobj == NULL)
        return NULL;
    //根據創建PyModuleObject
    module = PyModule_NewObject(nameobj);
    Py_DECREF(nameobj);
    return module;
}


PyObject *
PyModule_NewObject(PyObject *name)
{	
    //創建一個module對象
    PyModuleObject *m;
    //申請空間
    m = PyObject_GC_New(PyModuleObject, &PyModule_Type);
    if (m == NULL)
        return NULL;
    //設置相應屬性, 初始化為NULL
    m->md_def = NULL;
    m->md_state = NULL;
    m->md_weaklist = NULL;
    m->md_name = NULL;
    //屬性字典
    m->md_dict = PyDict_New();
    //調用module_init_dict
    if (module_init_dict(m, m->md_dict, name, NULL) != 0)
        goto fail;
    PyObject_GC_Track(m);
    return (PyObject *)m;

 fail:
    Py_DECREF(m);
    return NULL;
}

static int
module_init_dict(PyModuleObject *mod, PyObject *md_dict,
                 PyObject *name, PyObject *doc)
{
    _Py_IDENTIFIER(__name__);
    _Py_IDENTIFIER(__doc__);
    _Py_IDENTIFIER(__package__);
    _Py_IDENTIFIER(__loader__);
    _Py_IDENTIFIER(__spec__);

    if (md_dict == NULL)
        return -1;
    if (doc == NULL)
        doc = Py_None;
	//模塊的一些屬性、__name__、__doc__等等
    if (_PyDict_SetItemId(md_dict, &PyId___name__, name) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___doc__, doc) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___package__, Py_None) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___loader__, Py_None) != 0)
        return -1;
    if (_PyDict_SetItemId(md_dict, &PyId___spec__, Py_None) != 0)
        return -1;
    if (PyUnicode_CheckExact(name)) {
        Py_INCREF(name);
        Py_XSETREF(mod->md_name, name);
    }

    return 0;
}

這里雖然創建了一個module對象,但是這僅僅是一個空的module對象,卻並沒有包含相應的操作和數據。我們看到只設置了name和doc等屬性。

設置module對象

在PyModule_New結束之后,程序繼續執行 _PyModule_CreateInitialized 下面的代碼,然后我們知道通過 PyModule_AddFunctions 完成了對__builtins__幾乎全部屬性的設置。這個設置的屬性依賴於第二個參數methods,在這里為builtin_methods。然后會遍歷builtin_methods,並處理每一項元素,我們還是來看看長什么樣子。

//Python/bltinmodule.c

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

怎么樣,是不是看到了玄機。

總結一下就是:在 Py_NewInterpreter 中調用 new_interpreter 函數,然后在 new_interpreter 這個函數里面,通過 PyInterpreterState_New 創建 PyInterpreterState ,然后傳遞 PyInterpreterState 調用 PyThreadState_New 得到 PyThreadState 對象。

接着就是執行各種初始化動作,然后在 new_interpreter 中調用 _PyBuiltin_Init 設置內建屬性,在代碼的最后會設置大量的內置屬性(函數、對象)。但是有幾個卻不在里面,比如:dir、getattr等等。所以中間調用的 _PyModule_CreateInitialized 不僅僅是初始化一個module對象,還會在初始化之后將我們沒有看到的一些屬性設置進去,在 _PyModule_CreateInitialized 里面,先是使用 PyModule_New 創建一個PyModuleObject,在里面設置了name和doc等屬性之后,再通過 PyModule_AddFunctions 設置methods,在這里面我們看到了dir、getattr等內置屬性。當這些屬性設置完之后,退回到 _PyBuiltin_Init 函數中,再設置剩余的大量屬性。之后,__builtins__就完成了。

另外 builtin_methods 是一個 PyMethodDef 類型的數組,里面是一個個的 PyMethodDef 結構體,而這個結構體定義在 Include/methodobject.h 中。

struct PyMethodDef {
    /* 內置的函數或者方法名 */
    const char  *ml_name;   
    /* 實現對應邏輯的C函數,但是需要轉成PyCFunction類型,主要是為了更好的處理關鍵字參數 */
    PyCFunction ml_meth;    
    
    /* 參數類型 
    #define METH_VARARGS  0x0001  擴展位置參數
    #define METH_KEYWORDS 0x0002  擴展關鍵字參數
    #define METH_NOARGS   0x0004  不需要參數
    #define METH_O        0x0008  需要一個參數
    #define METH_CLASS    0x0010  被classmethod裝飾
    #define METH_STATIC   0x0020  被staticmethod裝飾   
    */
    int         ml_flags;   
    
    //函數的__dic__
    const char  *ml_doc; 
};
typedef struct PyMethodDef PyMethodDef;

對於這里面每一個 PyMethodDef ,_PyModule_CreateInitialized 都會基於它創建一個 PyCFunctionObject 對象, 這個對象Python對函數指針的包裝, 當然里面好包含了其它信息。

typedef struct {
    PyObject_HEAD  //頭部信息
    PyMethodDef *m_ml;  //PyMethodDef
    PyObject    *m_self;  //self參數
    PyObject    *m_module;  //__module__屬性
    PyObject    *m_weakreflist;  //弱引用列表, 不討論
    vectorcallfunc vectorcall;
} PyCFunctionObject;

而 PyCFunctionObject 對象則是通過 PyCFunction_New 完成的,該函數位於 Objects/methodobject.c 中。

PyObject *
PyCFunction_New(PyMethodDef *ml, PyObject *self)
{
    return PyCFunction_NewEx(ml, self, NULL);
}


PyObject *
PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)
{
    vectorcallfunc vectorcall;
    //判斷參數類型
    switch (ml->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS))
    {
        case METH_VARARGS:
        case METH_VARARGS | METH_KEYWORDS:
            vectorcall = NULL;
            break;
        case METH_FASTCALL:
            vectorcall = cfunction_vectorcall_FASTCALL;
            break;
        case METH_FASTCALL | METH_KEYWORDS:
            vectorcall = cfunction_vectorcall_FASTCALL_KEYWORDS;
            break;
        case METH_NOARGS:
            vectorcall = cfunction_vectorcall_NOARGS;
            break;
        case METH_O:
            vectorcall = cfunction_vectorcall_O;
            break;
        default:
            PyErr_Format(PyExc_SystemError,
                         "%s() method: bad call flags", ml->ml_name);
            return NULL;
    }

    PyCFunctionObject *op;
    //我們看到這里也采用了緩存池的策略
    op = free_list;
    if (op != NULL) {
        free_list = (PyCFunctionObject *)(op->m_self);
        (void)PyObject_INIT(op, &PyCFunction_Type);
        numfree--;
    }
    else {
        //否則重新申請
        op = PyObject_GC_New(PyCFunctionObject, &PyCFunction_Type);
        if (op == NULL)
            return NULL;
    }
    //設置屬性
    op->m_weakreflist = NULL;
    op->m_ml = ml;
    Py_XINCREF(self);
    op->m_self = self;
    Py_XINCREF(module);
    op->m_module = module;
    op->vectorcall = vectorcall;
    _PyObject_GC_TRACK(op);
    return (PyObject *)op;
}

_PyBuiltin__Init 之后,Python會把PyModuleObject對象中維護的那個PyDictObject對象抽取出來,將其賦值給 interp -> builtins

//moduleobject.c
PyObject *
PyModule_GetDict(PyObject *m)
{
    PyObject *d;
    if (!PyModule_Check(m)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    d = ((PyModuleObject *)m) -> md_dict;
    assert(d != NULL);
    return d;
}


static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    //......
    PyObject *bimod = _PyImport_FindBuiltin("builtins", modules);
    if (bimod != NULL) {
        //通過PyModule_GetDict獲取屬性字典, 賦值給builtins
        interp->builtins = PyModule_GetDict(bimod);
        if (interp->builtins == NULL)
            goto handle_error;
        Py_INCREF(interp->builtins);
    }
    else if (PyErr_Occurred()) {
        goto handle_error;
    }
    //......
}

以后Python在需要訪問__builtins__時,直接訪問 interp->builtins 就可以了,不需要再到 interp->modules 里面去找了。因為對於內置函數、屬性的使用在Python中會比較頻繁,所以這種加速機制是很有效的。

創建sys module

Python在創建並設置了__builtins__之后,會照貓畫虎,用同樣的流程來設置sys module,並像設置 interp->builtins 一樣設置 interp->sysdict 。

//Python/pylifecycle.c
static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    //.......
    PyObject *sysmod = _PyImport_FindBuiltin("sys", modules);
    if (sysmod != NULL) {
        interp->sysdict = PyModule_GetDict(sysmod);
        if (interp->sysdict == NULL) {
            goto handle_error;
        }
        Py_INCREF(interp->sysdict);
        //設置
        PyDict_SetItemString(interp->sysdict, "modules", modules);
        if (_PySys_InitMain(runtime, interp) < 0) {
            return _PyStatus_ERR("can't finish initializing sys");
        }
    }
    //.......
}

Python在創建了sys module之后,會在此module中設置一個Python搜索module時的默認路徑集合。

//Python/pylifecycle.c
static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    //.......
    status = add_main_module(interp);
    //.......
}    


static PyStatus
add_main_module(PyInterpreterState *interp)
{
    PyObject *m, *d, *loader, *ann_dict;
    //將__main__添加進sys.modules中
    m = PyImport_AddModule("__main__");
    if (m == NULL)
        return _PyStatus_ERR("can't create __main__ module");

    d = PyModule_GetDict(m);
    ann_dict = PyDict_New();
    if ((ann_dict == NULL) ||
        (PyDict_SetItemString(d, "__annotations__", ann_dict) < 0)) {
        return _PyStatus_ERR("Failed to initialize __main__.__annotations__");
    }
    Py_DECREF(ann_dict);

    if (PyDict_GetItemString(d, "__builtins__") == NULL) {
        PyObject *bimod = PyImport_ImportModule("builtins");
        if (bimod == NULL) {
            return _PyStatus_ERR("Failed to retrieve builtins module");
        }
        if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {
            return _PyStatus_ERR("Failed to initialize __main__.__builtins__");
        }
        Py_DECREF(bimod);
    }
    loader = PyDict_GetItemString(d, "__loader__");
    if (loader == NULL || loader == Py_None) {
        PyObject *loader = PyObject_GetAttrString(interp->importlib,
                                                  "BuiltinImporter");
        if (loader == NULL) {
            return _PyStatus_ERR("Failed to retrieve BuiltinImporter");
        }
        if (PyDict_SetItemString(d, "__loader__", loader) < 0) {
            return _PyStatus_ERR("Failed to initialize __main__.__loader__");
        }
        Py_DECREF(loader);
    }
    return _PyStatus_OK();
}

根據我們使用Python的經驗,我們知道最終Python肯定會創建一個PyListObject對象,也就是Python中的sys.path,里面包含了一組PyUnicodeObject,每一個PyUnicodeObject的內容就代表了一個搜索路徑。但是這一步不是在這里完成的,至於是在哪里完成的,我們后面會說。

另外,我們需要注意的是:在上面的邏輯中,解釋器將__main__這個模塊添加進去了,這個__main__估計不用我多說了。之前在 PyModule_New 中,創建一個PyModuleObject對象之后,會在其屬性字典(md_dict獲取)中插入一個名為"__name__"的key,value就是 "__main__"。但是對於當然模塊來說,這個模塊也可以叫做__main__。

name = "神楽七奈"
import __main__
print(__main__.name)  # 神楽七奈

import sys
print(sys.modules["__main__"] is __main__)  # True

我們發現這樣也是可以導入的,因為這個__main__就是這個模塊本身。

static PyStatus
add_main_module(PyInterpreterState *interp)
{
    PyObject *m, *d, *loader, *ann_dict;
    //創建__main__ module,並將其插入到interp->modules中
    m = PyImport_AddModule("__main__");
    if (m == NULL)
        return _PyStatus_ERR("can't create __main__ module");
    //獲取__main__的屬性字典
    d = PyModule_GetDict(m);
    
    //獲取interp->modules中的__builtins__ module
    if (PyDict_GetItemString(d, "__builtins__") == NULL) {
        PyObject *bimod = PyImport_ImportModule("builtins");
        if (bimod == NULL) {
            return _PyStatus_ERR("Failed to retrieve builtins module");
        }
        //將("__builtins__", __builtins__)插入到__main__ module的dict中
        if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {
            return _PyStatus_ERR("Failed to initialize __main__.__builtins__");
        }
        Py_DECREF(bimod);
    }

    //......
}

因此我們算是知道了,為什么python xxx.py執行的時候,__name__是__main__了,因為我們這里設置了。而Python沿着名字空間尋找的時候,最終會在__main__的local空間中發現__name__,且值為字符串"__main__"。但如果是以import的方式加載的,那么__name__則不是"__main__",而是模塊名,后面我們會繼續說。

2.png

其實這個__main__我們是再熟悉不過的了,當輸入dir()的時候,就會顯示__main__的內容。dir是可以不加參數的,如果不加參數,那么默認訪問當前的py文件,也就是__main__。

>>> __name__
'__main__'
>>>
>>> __builtins__.__name__
'builtins'
>>>
>>> import numpy as np
>>> np.__name__
'numpy'
>>>

所以說,訪問模塊就類似訪問變量一樣。modules里面存放了所有的(module name, PyModuleObject),當我們調用np的時候,是會找到name為"numpy"的值,然后這個值里面也維護了一個字典,其中就有一個key為__name__的entry。

設置site-specific的module的搜索路徑

Python是一個非常開放的體系,它的強大來源於豐富的第三方庫,這些庫由外部的py文件來提供,當使用這些第三方庫的時候,只需要簡單的進行import即可。一般來說,這些第三方庫都放在/lib/site-packages中,如果程序想使用這些庫,直接把庫放在這里面即可。

但是到目前為止,我們好像也沒看到python將site-packages路徑設置到搜索路徑里面去啊。其實在完成了__main__的創建之后,Python才騰出手來,收拾這個site-package。這個關鍵的動作在於Python的一個標准庫:site.py。

我們先來將Lib目錄下的site.py刪掉,然后導入一個第三方模塊,看看會有什么后果。

因此我們發現,Python在初始化的過程中確實導入了site.py,所以才有了如下的輸出。而這個site.py也正是Python能正確加載位於site-packages目錄下第三方包的關鍵所在。我們可以猜測,應該就是這個site.py將site-packages目錄加入到了前面的sys.path中,而這個動作是由 init_import_size 完成的。

static PyStatus
new_interpreter(PyThreadState **tstate_p)
{
    //......
        if (config->site_import) {
            status = init_import_size();
            if (_PyStatus_EXCEPTION(status)) {
                return status;
            }
        }
    //......
}


static PyStatus
init_import_size(void)
{
    PyObject *m;
    m = PyImport_ImportModule("site");
    if (m == NULL) {
        //這里的報錯信息是不是和上圖中顯示的一樣呢?
        return _PyStatus_ERR("Failed to import the site module");
    }
    Py_DECREF(m);
    return _PyStatus_OK();
}

init_import_size 中,只調用了 PyImport_ImportModule 函數,這個函數是Python中import機制的核心所在。PyImport_ImportModule("numpy")等價於python中的 import numpy 即可。

激活python虛擬機

Python運行方式有兩種,一種是在命令行中運行的交互式環境;另一種則是以python xxx.py方式運行腳本文件。盡管方式不同,但是卻殊途同歸,進入同一個字節碼虛擬機。

Python在 Py_Initialize 完成之后,最終會通過 pymain_run_file 調用 PyRun_AnyFileExFlags

//Modules/main.c
static int
pymain_run_file(PyConfig *config, PyCompilerFlags *cf)
{	
    //那么獲取文件名
    const wchar_t *filename = config->run_filename;
    if (PySys_Audit("cpython.run_file", "u", filename) < 0) {
        return pymain_exit_err_print();
    }
    //打開文件
    FILE *fp = _Py_wfopen(filename, L"rb");
    //如果fp為NULL, 證明文件打開失敗
    if (fp == NULL) {
        char *cfilename_buffer;
        const char *cfilename;
        int err = errno;
        cfilename_buffer = _Py_EncodeLocaleRaw(filename, NULL);
        if (cfilename_buffer != NULL)
            cfilename = cfilename_buffer;
        else
            cfilename = "<unprintable file name>";
        fprintf(stderr, "%ls: can't open file '%s': [Errno %d] %s\n",
                config->program_name, cfilename, err, strerror(err));
        PyMem_RawFree(cfilename_buffer);
        return 2;
    }
    //......
    //調用PyRun_AnyFileExFlags
    int run = PyRun_AnyFileExFlags(fp, filename_str, 1, cf);
    Py_XDECREF(bytes);
    return (run != 0);
}


//Python/pythonrun.c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    //根據fp是否代表交互環境,對程序進行流程控制
    if (Py_FdIsInteractive(fp, filename)) {
        //如果是交互環境,那么調用PyRun_InteractiveLoopFlags
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        //否則說明是一個普通的python腳本,執行PyRun_SimpleFileExFlags
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);
}

我們看到交互式和py腳本式走的兩條不同的路徑,但是別着急,最終你會看到它們又會分久必合、走向同一條路徑。

交互式運行

先來看看交互式運行時候的情形,不過在此之前先來看一下提示符。

>>> a = 1
>>> if a == 1:
...     pass
...
>>>
>>> import sys
>>> sys.ps1 = "matsuri:"
matsuri:a = 1
matsuri:a
1
matsuri:
matsuri:sys.ps2 = "fubuki:"
matsuri:if a == 1:
fubuki:    pass
fubuki:
matsuri:

我們每輸入一行,開頭都是>>> ,這個是sys.ps1,而輸入語句塊的時候,沒輸入完的時候,那么顯示...,這個是sys.ps2。如果修改了,那么就是我們自己定義的了。

int
PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
{
    //....
    //創建交互式提示符 
    v = _PySys_GetObjectId(&PyId_ps1);
    if (v == NULL) {
        _PySys_SetObjectId(&PyId_ps1, v = PyUnicode_FromString(">>> "));
        Py_XDECREF(v);
    }
    //同理這個也是一樣
    v = _PySys_GetObjectId(&PyId_ps2);
    if (v == NULL) {
        _PySys_SetObjectId(&PyId_ps2, v = PyUnicode_FromString("... "));
        Py_XDECREF(v);
    }
    err = 0;
    do {
        //這里就進入了交互式環境,我們看到每次都調用了PyRun_InteractiveOneObjectEx
        //直到下面的ret != E_EOF不成立 停止循環,一般情況就是我們輸入exit()圖此處了
        ret = PyRun_InteractiveOneObjectEx(fp, filename, flags);
        if (ret == -1 && PyErr_Occurred()) {
            if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
                if (++nomem_count > 16) {
                    PyErr_Clear();
                    err = -1;
                    break;
                }
            } else {
                nomem_count = 0;
            }
            PyErr_Print();
            flush_io();
        } else {
            nomem_count = 0;
        }
    //......
    } while (ret != E_EOF);
    Py_DECREF(filename);
    return err;
}


static int
PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
                             PyCompilerFlags *flags)
{
    PyObject *m, *d, *v, *w, *oenc = NULL, *mod_name;
    mod_ty mod;
    PyArena *arena;
    const char *ps1 = "", *ps2 = "", *enc = NULL;
    int errcode = 0;
    _Py_IDENTIFIER(encoding);
    _Py_IDENTIFIER(__main__);

    mod_name = _PyUnicode_FromId(&PyId___main__); /* borrowed */
    if (mod_name == NULL) {
        return -1;
    }

    if (fp == stdin) {
        //......
    }
    v = _PySys_GetObjectId(&PyId_ps1);
    if (v != NULL) {
        //......
    }
    w = _PySys_GetObjectId(&PyId_ps2);
    if (w != NULL) {
        //.....
    }
    //編譯用戶在交互式環境下輸入的python語句
    arena = PyArena_New();
    if (arena == NULL) {
        Py_XDECREF(v);
        Py_XDECREF(w);
        Py_XDECREF(oenc);
        return -1;
    }
    //生成抽象語法樹
    mod = PyParser_ASTFromFileObject(fp, filename, enc,
                                     Py_single_input, ps1, ps2,
                                     flags, &errcode, arena);
    Py_XDECREF(v);
    Py_XDECREF(w);
    Py_XDECREF(oenc);
    if (mod == NULL) {
        PyArena_Free(arena);
        if (errcode == E_EOF) {
            PyErr_Clear();
            return E_EOF;
        }
        return -1;
    }
    //獲取<module __main__>中維護的dict
    m = PyImport_AddModuleObject(mod_name);
    if (m == NULL) {
        PyArena_Free(arena);
        return -1;
    }
    d = PyModule_GetDict(m);
    //執行用戶輸入的python語句
    v = run_mod(mod, filename, d, d, flags, arena);
    PyArena_Free(arena);
    if (v == NULL) {
        return -1;
    }
    Py_DECREF(v);
    flush_io();
    return 0;
}

我們發現在run_mod之前,python會將__main__中維護的PyDictObject對象取出,作為參數傳遞給run_mod,這個參數關系極為重要,實際上這里的參數d就將作為Python虛擬機開始執行時當前活動的frame對象的local名字空間和global名字空間。

腳本文件運行方式

接下來,我們看一看直接運行腳本文件的方式。

//.include/compile.h
#define Py_file_input 257


//Python/pythonrun.c
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                        PyCompilerFlags *flags)
{
    PyObject *m, *d, *v;
    const char *ext;
    int set_file_name = 0, ret = -1;
    size_t len;
    //__main__就是當前文件
    m = PyImport_AddModule("__main__");
    if (m == NULL)
        return -1;
    Py_INCREF(m);
    //還記得這個d嗎?當前活動的frame對象的local和global名字空間
    d = PyModule_GetDict(m);
    //在__main__中設置__file__屬性
    if (PyDict_GetItemString(d, "__file__") == NULL) {
        PyObject *f;
        f = PyUnicode_DecodeFSDefault(filename);
        if (f == NULL)
            goto done;
        if (PyDict_SetItemString(d, "__file__", f) < 0) {
            Py_DECREF(f);
            goto done;
        }
        if (PyDict_SetItemString(d, "__cached__", Py_None) < 0) {
            Py_DECREF(f);
            goto done;
        }
        set_file_name = 1;
        Py_DECREF(f);
    }
    len = strlen(filename);
    ext = filename + len - (len > 4 ? 4 : 0);
    //如果是pyc
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        FILE *pyc_fp;
        //二進制模式打開
        if (closeit)
            fclose(fp);
        if ((pyc_fp = _Py_fopen(filename, "rb")) == NULL) {
            fprintf(stderr, "python: Can't reopen .pyc file\n");
            goto done;
        }

        if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) {
            fprintf(stderr, "python: failed to set __main__.__loader__\n");
            ret = -1;
            fclose(pyc_fp);
            goto done;
        }
        v = run_pyc_file(pyc_fp, filename, d, d, flags);
    } else {
        if (strcmp(filename, "<stdin>") != 0 &&
            set_main_loader(d, filename, "SourceFileLoader") < 0) {
            fprintf(stderr, "python: failed to set __main__.__loader__\n");
            ret = -1;
            goto done;
        }
        //執行腳本文件
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }
    //.......
}


PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    PyObject *ret = NULL;
    //......
    //編譯
    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena);
    if (closeit)
        fclose(fp);
    if (mod == NULL) {
        goto exit;
    }
    //執行, 依舊是調用了runmod
    ret = run_mod(mod, filename, globals, locals, flags, arena);

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

很顯然,腳本文件和交互式之間的執行流程是不同的,但是最終都進入了run_mod,而且同樣也將與__main__中維護的PyDictObject對象作為local名字空間和global名字空間傳入了run_mod。

啟動虛擬機

是的你沒有看錯,下面才是啟動虛擬機,之前做了那么工作都是前戲。

static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    PyCodeObject *co;
    PyObject *v;
    //基於ast編譯字節碼指令序列,創建PyCodeObject對象
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);
    if (co == NULL)
        return NULL;

    if (PySys_Audit("exec", "O", co) < 0) {
        Py_DECREF(co);
        return NULL;
    }
	
    //創建PyFrameObject,執行PyCodeObject對象中的字節碼指令序列
    v = run_eval_code_obj(co, globals, locals);
    Py_DECREF(co);
    return v;
}

run_mod接手傳來的ast,然后傳到 PyAST_CompileObject 中,創建了一個我們已經非常熟悉的PyCodeObject對象。關於這個完整的編譯過程,就又是另一個話題了,總之先是scanner進行詞法分析、將源代碼切分成一個個的token,然后parser在詞法分析之后的結果之上進行語法分析、通過切分好的token生成抽象語法樹(AST,abstract syntax tree),然后將AST編譯PyCodeObject對象,最后再由虛擬機執行。知道這么一個大致的流程即可,至於到底是怎么分詞、怎么建立語法樹的,這就又是一個難點了,個人覺得甚至比研究Python虛擬機還難。有興趣的話可以去看Python源碼中Parser目錄,如果能把Python的分詞、語法樹的建立給了解清楚,那我覺得你完全可以手寫一個正則表達式的引擎、以及各種模板語言。

而接下來,Python已經做好一切工作,開始通過 run_eval_code_obj 着手喚醒字節碼虛擬機。

static PyObject *
run_eval_code_obj(PyCodeObject *co, PyObject *globals, PyObject *locals)
{
    PyObject *v;
    //......
    v = PyEval_EvalCode((PyObject*)co, globals, locals);
    if (!v && PyErr_Occurred() == PyExc_KeyboardInterrupt) {
        _Py_UnhandledKeyboardInterrupt = 1;
    }
    return v;
}

函數中調用了 PyEval_EvalCode,根據前面介紹函數的時候,我們知道最終一定會走到 PyEval_EvalFrameEx

從操作系統創建進程,進程創建線程,線程設置builtins(包括設置__name__、內建對象、內置函數方法等等)、設置緩存池,然后各種初始化,設置搜索路徑。最后分詞、編譯、激活虛擬機執行。而執行的這個過程就是曾經與我們朝夕相處的 PyEval_EvalFrameEx ,掌控Python世界中無數對象的生生滅滅。參數f就是PyFrameObject對象,我們曾經探索了很久,現在一下子就回到了當初。有種夢回棧幀對象的感覺。目前的話,Python的骨架我們已經看清了,雖然還有很多細節隱藏在幕后。至少神秘的面紗已經被撤掉了。

名字空間

現在我們來看一下有趣的東西,看看在激活字節碼虛擬機、創建 PyFrameObject 對象時,所設置的3個名字空間:local、global、builtin。

//Objects/frameobject.c
PyFrameObject*
PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
            PyObject *globals, PyObject *locals)
{
    PyFrameObject *f = _PyFrame_New_NoTrack(tstate, code, globals, locals);
    if (f)
        _PyObject_GC_TRACK(f);
    return f;
}


PyFrameObject* _Py_HOT_FUNCTION
_PyFrame_New_NoTrack(PyThreadState *tstate, PyCodeObject *code,
                     PyObject *globals, PyObject *locals)
{
    PyFrameObject *back = tstate->frame;
    PyFrameObject *f;
    PyObject *builtins;
    Py_ssize_t i;

    //設置builtin名字空間
    if (back == NULL || back->f_globals != globals) {
        //但是我們發現設置builtins,居然是從globals里面獲取的
        //帶着這個疑問,看看下面更大的疑問        
        builtins = _PyDict_GetItemIdWithError(globals, &PyId___builtins__);
        //......
    }
    else {
        /* If we share the globals, we share the builtins.
           Save a lookup and a call. */
        builtins = back->f_builtins;
        assert(builtins != NULL);
        Py_INCREF(builtins);
    }
    //.......
    //設置builtins
    f->f_builtins = builtins;
    //....
    //設置globals
    f->f_globals = globals;

    if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) ==
        (CO_NEWLOCALS | CO_OPTIMIZED))
        ; /* f_locals = NULL; will be set by PyFrame_FastToLocals() */
    else if (code->co_flags & CO_NEWLOCALS) {
        locals = PyDict_New();
        if (locals == NULL) {
            Py_DECREF(f);
            return NULL;
        }
        f->f_locals = locals;
    }
    else {
        if (locals == NULL)
            //如果locals為NULL,那么等同於globals,顯然這是針對模塊來的
            locals = globals;
        Py_INCREF(locals);
        f->f_locals = locals;
    }

    f->f_lasti = -1;
    f->f_lineno = code->co_firstlineno;
    f->f_iblock = 0;
    f->f_executing = 0;
    f->f_gen = NULL;
    f->f_trace_opcodes = 0;
    f->f_trace_lines = 1;

    return f;
}

我們說內置名字空間是從global名字空間里面獲取的,我們用Python來演示一下。

# 代表了globals()里面存放了builtins
print(globals()["__builtins__"])  # <module 'builtins' (built-in)>

# 我們說builtins里面包含了所有的內置對象、函數等等,顯然調用int是可以的
print(globals()["__builtins__"].int("123"))  # 123

# 但是,我居然能從builtins里面拿到globals
# 不過也很好理解,因為globals是一個內置函數,肯定是在builtins里面
print(globals()["__builtins__"].globals)  # <built-in function globals>

# 於是拿到了globals,繼續調用,然后獲取__builtins__,又拿到了builtins,而且我們是可以調用list的
print(globals()["__builtins__"].globals()["__builtins__"].list("abcd"))  # ['a', 'b', 'c', 'd']

所以不管套娃多少次,都是可以的,因為它們都是指針。

可以看到builtin和global空間里面都存儲一個能夠獲取對方空間的一個函數指針, 所以這兩者是並不沖突的。當然除此之外,還有一個__name__,注意我們之前說設置__name__只是builtins的__name__,並不是當前模塊的。

# 我們看到,builtins里面獲取的__name__居然不是__main__,而是builtins
print(globals()["__builtins__"].__name__)  # builtins

# 首先按照local  global builtin的順序查找是沒問題的
# 而對於模塊來說,我們知道local空間為NULL的話,然后直接把global空間交給local空間了
# 而local里面有__name__,就是__main__,所以__name__和builtins.__name__不是一個東西
print(globals()["__name__"])  # __main__
# 初始化builtins的時候,那個__name__指的是builtins這個PyModuleObject的__name__
# 而對於我們py文件這個模塊來說,__name__是設置在global名字空間里面的

# 如果將global空間或者local空間里面的__name__刪掉,那么按照順序就會尋找builtin里面的__name__,此時就會打印builtins了。
globals().pop("__name__")
print(__name__)  # builtins

所以我們看到__name__這個屬性是在啟動之后動態設置的,如果執行的文件和該文件是同一個文件,那么__name__就會是__main__;如果不是同一個文件,證明這個文件是作為模塊被導入進來的,那么此時它的__name__就是文件名。

更多細節可以前往源碼中查看,Python運行環境初始化還是比較復雜的。

小結

這一次我們說了Python運行環境的初始化,或者說當Python啟動的時候都做了哪些事情。可以看到,做的事情不是一般的多,真的准備了大量的工作。因為Python是動態語言,這就意味很多操作都要發生在運行時。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM