我認識的python(4)

渣渣強發表於2018-07-14

直譯器篇

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位元組碼內容。

我認識的python(4)

相關文章