直譯器篇
code物件和pyc檔案
PyCodeObject和pyc檔案其實只是原始檔編譯後的不同體現,前者是記憶體中的結果,後者是磁碟存在的結構。
一個PyCodeObject的邊界在於一個新的作用域,所以模組,函式,類都有其PyCodeObject的結構。這裡有一個重要的概念就是名稱空間,代表符號的上下文,名稱空間一個套一個形成了一條名稱空間鏈。
Field | Content |
---|---|
co_argcount | Code Block 的位置引數的個數,比如說一個函式的位置引數個數 |
co_nlocals | Code Block 中區域性變數的個數,包括位置引數的個數 |
co_stacksize | 執行該段Code Block需要的棧空間 |
co_flags | 標示 |
co_code | 位元組碼 |
co_consts | PyTupleObject,儲存Code Block中所有的常量 |
co_names | PyTupleObject,儲存Code Block中所有的符號 |
co_varnames | Code Block中區域性變數名集合 |
co_freevars | python閉包需要用到的東西 |
co_cellvars | Code Block中內嵌函式使用的區域性變數名集合 |
co_filename | Code Block完整路徑 |
co_name | Code Block名稱 |
co_firstlineno | Code Block在.py的起始位置 |
co_lnotab | 位元組碼與.py檔案中source code行號對應關係 |
PyCodeObject物件中的co_consts包含python檔案中定義的常量物件,常量物件即是那些非PyString物件以外的所有物件,字串則是使用索引對應到co_names。co_names會記錄所有的符號,co_varnames會記錄所有區域性變數符號。
pyc的生成
其實瞭解過rpc應該指導,rpc序列化確定訊息邊界常用的方式是使用長度字首法或者特殊符號分割法。類似http協議的序列化head部分就是採取特殊符號分割法,而pyc的序列化方式是長度字首法(用型別類似可以表達長度的概念)。
大致的寫入過程如下:
static void write_compiled_module(PyCodeObject *co, char *cpathname, long mtime){
FILE *fp;
fp = open_exclusive(cpathname);
PyMarsha1_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION);
PyMarsha1_WriteLongToFile(mtime, fp, Py_MARSHAL_VERSION);
PyMarsha1_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION);
fflush(fp);
fclose(fp);
}
PyMarsha1_WriteObjectToFile過程則是解析不同的型別,然後呼叫不同的函式進行序列化,long型別的序列化如下:
static void w_long(long x, WFILE *p){
w_byte((char)(x & 0xff), p);
w_byte((char)((x>>8) & 0xff), p);
w_byte((char)((x>>16) & 0xff), p);
w_byte((char)((x>>24) & 0xff), p);
}
if(PyInt_check(v)){
w_byte(TYPE_INT, p);
w_long(x, p);
}
對於module裡面的PyCode則是儲存在co_consts中,分析這個欄位如果發現PyCode物件,則遞迴進去。
複製程式碼
位元組碼
通過opcode模組的opmap可以看到位元組碼對應的值。
python某些位元組碼需要使用到引數,那麼這個關係是用下面的方式體現的。
#define HAVE_ARGUMENT 90
#define HAS_ARG(op) ((op) >= HAVE_ARGUMENT)
複製程式碼
通過dis模組可以讀取出視覺化的python位元組碼內容。