楔子
我們之前分析了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 關聯了 PyInterpreterState , PyInterpreterState 也關聯了 PyInterpreterState 。到目前為止,僅有的兩個對象建立起了聯系。對應到Windows,或者說操作系統,我們說進程和線程建立了聯系
在 PyInterpreterState 和 PyThreadState 建立了聯系之后,那么就很容易在 PyInterpreterState 和PyThreadState 之間穿梭。並且在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 算是有了一個階段性的成功,我們創建了代表進程和線程概念的 PyInterpreterState 和 PyThreadState 對象,並且在它們之間建立的聯系。下面, new_interpreter 將進行入另一個環節,設置系統module。
創建__builtins__
在 new_interpreter 中當Python解釋器創建了 PyInterpreterState 和 PyThreadState 對象之后,就會開始設置系統的__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__",而是模塊名,后面我們會繼續說。
其實這個__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是動態語言,這就意味很多操作都要發生在運行時。