閱讀源碼版本python 3.8.3html
參考書籍<<Python源碼剖析>>python
參考書籍<<Python學習手冊 第4版>>c++
官網文檔目錄介紹git
Doc目錄主要是官方文檔的說明。github
Include:目錄主要包括了Python的運行的頭文件。web
Lib:目錄主要包括了用Python實現的標準庫。shell
Modules: 該目錄中包含了全部用C語言編寫的模塊,好比random、cStringIO等。Modules中的模塊是那些對速度要求很是嚴格的模塊,而有一些對速度沒有太嚴格要求的模塊,好比os,就是用Python編寫,而且放在Lib目錄下的緩存
Objects:該目錄中包含了全部Python的內建對象,包括整數、list、dict等。同時,該目錄還包括了Python在運行時須要的全部的內部使用對象的實現。數據結構
Parser:該目錄中包含了Python解釋器中的Scanner和Parser部分,即對Python源碼進行詞法分析和語法分析的部分。除了這些,Parser目錄下還包含了一些有用的工具,這些工具可以根據Python語言的語法自動生成Python語言的詞法和語法分析器,將python文件編譯生成語法樹等相關工做。app
Programs目錄主要包括了python的入口函數。
Python:目錄主要包括了Python動態運行時執行的代碼,裏面包括編譯、字節碼解釋器等工做。
Python啓動是由Programs下的python.c文件中的main函數開始執行
/* Minimal main program -- everything is loaded from the library */ #include "Python.h" #include "pycore_pylifecycle.h" #ifdef MS_WINDOWS int wmain(int argc, wchar_t **argv) { return Py_Main(argc, argv); } #else int main(int argc, char **argv) { return Py_BytesMain(argc, argv); } #endif
int Py_Main(int argc, wchar_t **argv) { ... return pymian_main(&args); } static int pymain_main(_PyArgv *args) { PyStatus status = pymain_init(args); // 初始化 if (_PyStatus_IS_EXIT(status)) { pymain_free(); return status.exitcode; } if (_PyStatus_EXCEPTION(status)) { pymain_exit_error(status); } return Py_RunMain(); }
pyinit_core
核心部分
pycore_init_runtime
, 同時生成HashRandompycore_create_interpreter
pycore_init_types
_PySys_Create
pycore_init_builtins
_PyBuiltins_AddExceptions
Python3.8 對Python解釋器的初始化作了重構PEP 587-Python初始化配置
int Py_RunMain(void) { int exitcode = 0; pymain_run_python(&exitcode); //執行python腳本 if (Py_FinalizeEx() < 0) { // 釋放資源 /* Value unlikely to be confused with a non-error exit status or other special meaning */ exitcode = 120; } pymain_free(); // 釋放資源 if (_Py_UnhandledKeyboardInterrupt) { exitcode = exit_sigint(); } return exitcode; } static void pymain_run_python(int *exitcode) { // 獲取一個持有GIL鎖的解釋器 PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE(); /* pymain_run_stdin() modify the config */ ... // 添加sys_path等操做 if (config->run_command) { // 命令行模式 *exitcode = pymain_run_command(config->run_command, &cf); } else if (config->run_module) { // 模塊名 *exitcode = pymain_run_module(config->run_module, 1); } else if (main_importer_path != NULL) { *exitcode = pymain_run_module(L"__main__", 0); } else if (config->run_filename != NULL) { // 文件名 *exitcode = pymain_run_file(config, &cf); } else { *exitcode = pymain_run_stdin(config, &cf); } ... } /* Parse input from a file and execute it */ //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); // 執行腳本 } // 執行python .py文件 int PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit, PyCompilerFlags *flags) { ... if (maybe_pyc_file(fp, filename, ext, closeit)) { FILE *pyc_fp; /* Try to run a pyc file. First, re-open in binary */ ... v = run_pyc_file(pyc_fp, filename, d, d, flags); } else { /* When running from stdin, leave __main__.__loader__ alone */ ... 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) { ... // // 解析傳入的腳本,解析成AST mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0, flags, NULL, arena); ... // 將AST編譯成字節碼而後啓動字節碼解釋器執行編譯結果 ret = run_mod(mod, filename, globals, locals, flags, arena); ... } // 查看run_mode static PyObject * run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals, PyCompilerFlags *flags, PyArena *arena) { ... // 將AST編譯成字節碼 co = PyAST_CompileObject(mod, filename, flags, -1, arena); ... // 解釋執行編譯的字節碼 v = run_eval_code_obj(co, globals, locals); Py_DECREF(co); return v; }
新建test.py
def show(a): return a if __name__ == "__main__": print(show(10))
執行命令: python3 -m dis test.py
λ ppython3 -m dis test.py 3 0 LOAD_CONST 0 (<code object show at 0x000000E7FC89E270, file "test.py", line 3>) 2 LOAD_CONST 1 ('show') 4 MAKE_FUNCTION 0 6 STORE_NAME 0 (show) 7 8 LOAD_NAME 1 (__name__) 10 LOAD_CONST 2 ('__main__') 12 COMPARE_OP 2 (==) 14 POP_JUMP_IF_FALSE 28 8 16 LOAD_NAME 2 (print) 18 LOAD_NAME 0 (show) 20 LOAD_CONST 3 (10) 22 CALL_FUNCTION 1 24 CALL_FUNCTION 1 26 POP_TOP >> 28 LOAD_CONST 4 (None)
左邊3, 7, 8表示 test.py中的第一行和第二行,右邊表示python byte code
Include/opcode.h
發現總共有 163 個 opcode, 全部的 python 源文件(Lib庫中的文件)都會被編譯器翻譯成由 opcode 組成的 pyx 文件,並緩存在執行目錄,下次啓動程序若是源代碼沒有修改過,則直接加載這個pyx文件,這個文件的存在能夠加快 python 的加載速度。普通.py文件如咱們的test.py 是直接進行編譯解釋執行的,不會生成.pyc文件,想生成test.pyc 須要使用python內置的py_compile模塊來編譯該文件,或者執行命令python3 -m test.py
python生成.pyc文件
字節碼在python虛擬機中對應的是PyCodeObject
對象, .pyc文件是字節碼在磁盤上的表現形式。python編譯的過程當中,一個代碼塊就對應一個code對象,那麼如何肯定多少代碼算是一個Code Block呢? 編譯過程當中遇到一個新的命名空間或者做用域時就生成一個code對象,即類或函數都是一個代碼塊,一個code的類型結構就是PyCodeObject
, 參考Junnplus
/* Bytecode object */ typedef struct { PyObject_HEAD int co_argcount; /* #arguments, except *args */ // 位置參數的個數, int co_posonlyargcount; /* #positional only arguments */ int co_kwonlyargcount; /* #keyword only arguments */ int co_nlocals; /* #local variables */ int co_stacksize; /* #entries needed for evaluation stack */ int co_flags; /* CO_..., see below */ int co_firstlineno; /* first source line number */ PyObject *co_code; /* instruction opcodes */ PyObject *co_consts; /* list (constants used) */ PyObject *co_names; /* list of strings (names used) */ PyObject *co_varnames; /* tuple of strings (local variable names) */ PyObject *co_freevars; /* tuple of strings (free variable names) */ PyObject *co_cellvars; /* tuple of strings (cell variable names) */ /* The rest aren't used in either hash or comparisons, except for co_name, used in both. This is done to preserve the name and line number for tracebacks and debuggers; otherwise, constant de-duplication would collapse identical functions/lambdas defined on different lines. */ Py_ssize_t *co_cell2arg; /* Maps cell vars which are arguments. */ PyObject *co_filename; /* unicode (where it was loaded from) */ PyObject *co_name; /* unicode (name, for reference) */ PyObject *co_lnotab; /* string (encoding addr<->lineno mapping) See Objects/lnotab_notes.txt for details. */ void *co_zombieframe; /* for optimization only (see frameobject.c) */ PyObject *co_weakreflist; /* to support weakrefs to code objects */ /* Scratch space for extra data relating to the code object. Type is a void* to keep the format private in codeobject.c to force people to go through the proper APIs. */ void *co_extra; /* Per opcodes just-in-time cache * * To reduce cache size, we use indirect mapping from opcode index to * cache object: * cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1] */ // co_opcache_map is indexed by (next_instr - first_instr). // * 0 means there is no cache for this opcode. // * n > 0 means there is cache in co_opcache[n-1]. unsigned char *co_opcache_map; _PyOpcache *co_opcache; int co_opcache_flag; // used to determine when create a cache. unsigned char co_opcache_size; // length of co_opcache. } PyCodeObject;
Field | Content | Type |
---|---|---|
co_argcount | Code Block 的參數個數 | PyIntObject |
co_posonlyargcount | Code Block 的位置參數個數 | PyIntObject |
co_kwonlyargcount | Code Block 的關鍵字參數個數 | PyIntObject |
co_nlocals | Code Block 中局部變量的個數 | PyIntObject |
co_stacksize | Code Block 的棧大小 | PyIntObject |
co_flags | N/A | PyIntObject |
co_firstlineno | Code Block 對應的 .py 文件中的起始行號 | PyIntObject |
co_code | Code Block 編譯所得的字節碼 | PyBytesObject |
co_consts | Code Block 中的常量集合 | PyTupleObject |
co_names | Code Block 中的符號集合 | PyTupleObject |
co_varnames | Code Block 中的局部變量名集合 | PyTupleObject |
co_freevars | Code Block 中的自由變量名集合 | PyTupleObject |
co_cellvars | Code Block 中嵌套函數所引用的局部變量名集合 | PyTupleObject |
co_cell2arg | N/A | PyTupleObject |
co_filename | Code Block 對應的 .py 文件名 | PyUnicodeObject |
co_name | Code Block 的名字,一般是函數名/類名/模塊名 | PyUnicodeObject |
co_lnotab | Code Block 的字節碼指令於 .py 文件中 source code 行號對應關係 | PyBytesObject |
co_opcache_map | python3.8新增字段,存儲字節碼索引與CodeBlock對象的映射關係 | PyDictObject |
// Python\ceval.c PREDICTED(LOAD_CONST); -> line 943: #define PREDICTED(op) PRED_##op: FAST_DISPATCH(); -> line 876 #define FAST_DISPATCH() goto fast_next_opcode
額外收穫: c 語言中 ##和# 號 在marco 裏的做用能夠參考 這篇
在宏定義裏, ## 被稱爲鏈接符(concatenator) , a##b 表示將ab鏈接起來
a 表示把a轉換成字符串,即加雙引號,
因此LONAD_CONST這個指領根據宏定義展開以下:
case TARGET(LOAD_CONST): { PRED_LOAD_CONST: PyObject *value = GETITEM(consts, oparg); // 獲取一個PyObject* 指針對象 Py_INCREF(value); // 引用計數加1 PUSH(value); // 把剛剛建立的PyObject* push到當前的frame的stack上, 以便下一個指令從這個 stack 上面獲取 goto fast_next_opcode;
// Python\ceval.c main_loop: for (;;) { ... switch (opcode) { /* BEWARE! It is essential that any operation that fails must goto error and that all operation that succeed call [FAST_]DISPATCH() ! */ case TARGET(NOP): { FAST_DISPATCH(); } case TARGET(LOAD_FAST): { PyObject *value = GETLOCAL(oparg); if (value == NULL) { format_exc_check_arg(PyExc_UnboundLocalError, UNBOUNDLOCAL_ERROR_MSG, PyTuple_GetItem(co->co_varnames, oparg)); goto error; } Py_INCREF(value); PUSH(value); FAST_DISPATCH(); } case TARGET(LOAD_CONST): { PREDICTED(LOAD_CONST); PyObject *value = GETITEM(consts, oparg); Py_INCREF(value); PUSH(value); FAST_DISPATCH(); } ... } }
在 python 虛擬機中,解釋器主要在一個很大的循環中,不停地讀入 opcode, 並根據 opcode 執行對應的指令,當執行完全部指令虛擬機退出,程序也就結束了
過程描述:
main_loop
它的做用是不斷讀取編譯好的字節碼,並一條一條執行,相似CPU執行指令的過程。函數內部主要是一個switch結構,根據字節碼的不一樣執行不一樣的代碼如上所說,PyCodeObject
對象只是包含了字節碼指令集以及程序的相關靜態信息,虛擬機的執行還須要一個執行環境,即PyFrameObject
,也就是對系統棧幀的模擬。
堆中存的是對象。棧中存的是基本數據類型和堆中對象的引用。一個對象的大小是不可估計的,或者說是能夠動態變化的,可是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處)
內存中的堆棧和數據結構堆棧不是一個概念,能夠說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。
內存空間在邏輯上分爲三部分:代碼區,靜態數據區和動態數據區,動態數據區有分爲堆區和棧區
malloc
函數,python中的Pymalloc
typedef struct _frame{ PyObject_VAR_HEAD //"運行時棧"的大小是不肯定的, 因此用可變長的對象 struct _frame *f_back; //執行環境鏈上的前一個frame,不少個PyFrameObject鏈接起來造成執行環境鏈表 PyCodeObject *f_code; //PyCodeObject 對象,這個frame就是這個PyCodeObject對象的上下文環境 PyObject *f_builtins; //builtin名字空間 PyObject *f_globals; //global名字空間 PyObject *f_locals; //local名字空間 PyObject **f_valuestack; //"運行時棧"的棧底位置 PyObject **f_stacktop; //"運行時棧"的棧頂位置 //... int f_lasti; //上一條字節碼指令在f_code中的偏移位置 int f_lineno; //當前字節碼對應的源代碼行 //... //動態內存,維護(局部變量+cell對象集合+free對象集合+運行時棧)所須要的空間 PyObject *f_localsplus[1]; } PyFrameObject;
若是你想知道 PyFrameObject 中每一個字段的意義, 請參考 Junnplus' blog 或者直接閱讀源代碼,瞭解frame的執行過程能夠參考zpoint'blog.
名字空間其實是維護着變量名和變量值之間關係的PyDictObject對象。
f_builtins, f_globals, f_locals名字空間分別維護了builtin, global, local的name與對應值之間的映射關係。
每個 PyFrameObject對象都維護了一個 PyCodeObject對象,這代表每個 PyFrameObject中的動態內存空間對象都和源代碼中的一段Code相對應。
能夠經過sys._getframe([depth]), 獲取指定深度的PyFrameObject
對象
>>> import sys >>> frame = sys._getframe() >>> frame <frame object at 0x103ab2d48>
Local -> Enclosed -> Global -> Built-In
Local 表示局部變量
Enclosed 表示嵌套的變量
Global 表示全局變量
Built-In 表示內建變量
若是這幾個順序都取不到,就會拋出 ValueError
能夠在這個網站python執行可視化網站,觀察代碼執行流程,以及變量的轉換賦值狀況。
意外收穫: 以前知道pythonGIL , 遇到I/O阻塞時會釋放gil,如今從源碼中看到了對應的流程
if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) { /* Give another thread a chance */ if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) { Py_FatalError("ceval: tstate mix-up"); } drop_gil(ceval, tstate); /* Other threads may run now */ take_gil(ceval, tstate); /* Check if we should make a quick exit. */ exit_thread_if_finalizing(runtime, tstate); if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) { Py_FatalError("ceval: orphan tstate"); } } /* Check for asynchronous exceptions. */
參考: