源碼編譯
要看懂源碼,肯定要學會編譯源碼。先去官網下載源碼文件,然后解壓編譯安裝。linux環境下進解壓目錄依次運行:
- ./configure -prefix=<安裝路徑>
- make
- make install
至此一個可運行python就已經編譯完成,之后便可以愉快地改源碼了。
python運行流程
當我們運行python命令時,代碼流向是怎樣的呢?makefile中target指向Modules文件夾的python.c,
執行里面的Py_Main方法,Py_Main方法實現在main.c,主要是判斷命令參數執行不同操作,整個方法可以簡化成初始化環境、執行、清理環境:
Py_Initialize();
sts = PyRun_AnyFileExFlags(
fp,
filename == NULL ? "<stdin>" : filename,
filename != NULL, &cf) != 0;
}
Py_Finalize();
python有兩種運行模式,命令行模式和文件執行,最終都會調用PyRun_AnyFileFlags方法,參數里面的filename就對應著運行python abc.py這種模式。
PyRun_AnyFileFlags在Python目錄的pythonrun.c:
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
PyCompilerFlags *flags)
{
if (filename == NULL)
filename = "???";
if (Py_FdIsInteractive(fp, filename)) {
int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
if (closeit)
fclose(fp);
return err;
}
else
return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);
}
PyRun_InteractiveLoopFlags和PyRun_SimpleFileExFlags分別對應交互式命令行執行和文件執行,但兩者最終執行流程會匯合在一起。
我們先來看下Py_Initialize方法,初始化環境初始化了些什么。
python運行環境初始化
Py_Initialize也在pythonrun.c里,實際調用了Py_InitializeEx方法,初始化順序依次如下:
- hash算法 隨機數初始化
_PyRandom_Init();
這里只是初始化hash算法需要用到的隨機數環境,使得 dict 對象中 key 的順序每次啟動時隨機;而python的random模塊在Lib文件夾的random.py,random.py內部引用的是C生成python模塊,位于Modules的_randommodule.c,其中一個隨機數基礎方法random_random挺有意思,對應random(),把注釋也貼出來:
/* random_random is the function named genrand_res53 in the original code;
* generates a random number on [0,1) with 53-bit resolution; note that
* 9007199254740992 == 2**53; I assume they're spelling "/2**53" as
* multiply-by-reciprocal in the (likely vain) hope that the compiler will
* optimize the division away at compile-time. 67108864 is 2**26. In
* effect, a contains 27 random bits shifted left 26, and b fills in the
* lower 26 bits of the 53-bit numerator.
* The orginal code credited Isaku Wada for this algorithm, 2002/01/09.
*/
static PyObject *
random_random(RandomObject *self)
{
unsigned long a=genrand_int32(self)>>5, b=genrand_int32(self)>>6;
return PyFloat_FromDouble((a*67108864.0+b)*(1.0/9007199254740992.0));
}
PyFloat_FromDouble表示從double類型轉換成PyFloat對象,其中genrand_int32方法也是這種魔數風格,感覺很geek。
- 進程環境初始化
interp = PyInterpreterState_New();
這里只是初始化一個進程狀態鏈表,進程實現在Lib文件夾的multiprocessing和Modules文件夾的_multiprocessing,底層使用的是系統的進程,根據系統不同實現不同
- 線程環境初始化
tstate = PyThreadState_New(interp);
這里只是初始化一個線程狀態鏈表,線程實現在Modules的threadmodule.c,底層使用的是系統的線程,根據系統不同實現不同
- 類型初始化
_Py_ReadyTypes();
里面關鍵的方法PyType_Ready實現在Objects的typeobject.c,根據類型不同進行了初始化
- int、long等對象初始化
_PyFrame_Init()
_PyInt_Init()
_PyLong_Init()
PyByteArray_Init()
_PyFloat_Init()
_PyUnicode_Init()
除了_PyFrame_Init代表builtin_object,其他方法看名字應該就清楚其用途
- __builtin__ module初始化
bimod = _PyBuiltin_Init();
點進去就會發現都是些熟悉的面孔
- sys module初始化
sysmod = _PySys_Init();
- 初始化import環境
_PyImport_Init();
- 初始化內建異常
_PyExc_Init();
- 在sys module中添加一些對象用于import
_PyImportHooks_Init();
- 初始化信號機制
initsigs();
- 初始化warning機制
_PyWarnings_Init();
- 初始化main
initmain();
- 這個應該和GIL有關,具體要看下GIL
_PyGILState_Init(interp, tstate);
- site module初始化
initsite();
就是import Lib文件夾里site.py,目的是加載site-packages里的包,不同平臺情況不同
字節碼運行
初始完環境,就可以開始執行自己寫的代碼了。之前提到PyRun_InteractiveLoopFlags和PyRun_SimpleFileExFlags兩者最終執行流程會匯合在一起,這里把兩者代碼簡化一下:
PyRun_InteractiveLoopFlags
for (;;) {
ret = PyRun_InteractiveOneFlags(fp, filename, flags);
_PyDebug_PrintTotalRefs();
if (ret == E_EOF)
return 0;
}
內部PyRun_InteractiveOneFlags方法
arena = PyArena_New();
mod = PyParser_ASTFromFile(fp, filename,
Py_single_input, ps1, ps2,
flags, &errcode, arena);
m = PyImport_AddModule("__main__");
d = PyModule_GetDict(m);
v = run_mod(mod, filename, d, d, flags, arena);
PyArena_Free(arena);
PyRun_SimpleFileExFlags
m = PyImport_AddModule("__main__");
d = PyModule_GetDict(m);
if (maybe_pyc_file(fp, filename, ext, closeit)) {
v = run_pyc_file(fp, filename, d, d, flags);
} else {
v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
closeit, flags);
}
內部run_pyc_file方法
magic = PyMarshal_ReadLongFromFile(fp);
if (magic != PyImport_GetMagicNumber()) {
PyErr_SetString(PyExc_RuntimeError,
"Bad magic number in .pyc file");
return NULL;
}
(void) PyMarshal_ReadLongFromFile(fp);
v = PyMarshal_ReadLastObjectFromFile(fp);
co = (PyCodeObject *)v;
v = PyEval_EvalCode(co, globals, locals);
內部PyRun_FileExFlags方法
PyArena *arena = PyArena_New();
mod = PyParser_ASTFromFile(fp, filename, start, 0, 0,
flags, NULL, arena);
ret = run_mod(mod, filename, globals, locals, flags, arena);
PyArena_Free(arena);
看了代碼,可以發現只有兩種模式:根據pyc文件執行和調用run_mod方法。pyc概念就不多說了,pyc文件開頭會記錄magic數,用于兼容python版本問題;而調用run_mod之前的arena是python的內存管理單位,run_mod的代碼如下:
co = PyAST_Compile(mod, filename, flags, arena);
v = PyEval_EvalCode(co, globals, locals);
這樣所有的執行都匯合到了一起,調用PyEval_EvalCode方法。PyEval_EvalCode接收一個PyCodeObject對象,PyCodeObject對象是python代碼編譯結果,由此可見pyc文件其實是個PyCodeObject緩存,pyc從名字看也可以看出是簡稱嘛。PyEval_EvalCode內部調用了PyEval_EvalFrameEx方法,PyEval_EvalFrameEx就是最后的大魔王了。PyEval_EvalFrameEx是個很龐大的方法,還是先從PyCodeObject入手,使用dis模塊就可以知道PyCodeObject的結構了,比如print('hello,world')的輸出如下:
('const : ', ('hello,world', None))
('name : ', ())
2 0 LOAD_CONST 0 ('hello,world')
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 1 (None)
8 RETURN_VALUE
第一個是靜態變量,第二個是變量,第三個是字節碼,這個字節碼就是執行的核心。PyEval_EvalFrameEx中有個大switch塊,里面的case條件就對應著字節碼(所有的字節碼在這個篇文章可以看到)。
最后的Py_Finalize方法可以看成是Py_Initialize反過程。
本文到這里就結束了,python源碼內容豐富龐大,這篇文章只是淺顯地過了一下,目的是了解代碼執行原理、了解整個脈絡,具體細節可以參考《Python源碼剖析》這本書。