Python編譯結(jié)果之code對象與pyc文件詳解
1、Python程序執(zhí)行過程
與java類似,Python將.py編譯為字節(jié)碼,然后通過虛擬機執(zhí)行。編譯過程與虛擬機執(zhí)行過程均在python25.dll中。Python虛擬機比java更抽象,離底層更遠。
編譯過程不僅生成字節(jié)碼,還要包含常量、變量、占用棧的空間等,Pyton中編譯過程生成code對象PyCodeObject。將PyCodeObject寫入二進制文件,即.pyc。

有必要則寫入A.pyc指的是該.py是否只運行一次,如果import的模塊,肯定會生成.pyc。
2、PyCodeObject對象與.pyc文件
Python解釋器將.py程序編譯為PyCodeObject對象,具體過程與編譯原理類似。
typedef struct {
PyObject_HEAD
int co_argcount; // Code Block的參數(shù)的個數(shù),比如說一個函數(shù)的參數(shù)
int co_nlocals; // Code Block中局部變量的個數(shù)
int co_stacksize; // 執(zhí)行該段Code Block需要的??臻g
int co_flags; // N/A
PyObject *co_code; // Code Block編譯所得的byte code,以PyStringObject的形式存在
PyObject *co_consts; // PyTupleObject對象,保存Code Block中的常量
PyObject *co_names; // PyTupleObject對象,保存Code Block中的所有符號
PyObject *co_varnames; // Code Block中局部變量名集合
PyObject *co_freevars; // 實現(xiàn)閉包所需東西
PyObject *co_cellvars; // Code Block內(nèi)部嵌套函數(shù)所引用的局部變量名集合
PyObject *co_filename; // Code Block所對應的.py文件的完整路徑
PyObject *co_name; // Code Block的名字,通常是函數(shù)名或類名
int co_firstlineno; // Code Block在對應的.py文件中的起始行
PyObject *co_lnotab; // byte code與.py文件中source code行號的對應關系,以PyStringObject的形式存在
void *co_zombieframe;
PyObject *co_weakreflist;
} PyCodeObject;
一個Code Block生成一個PyCodeObject,進入一個名字空間成為進入一個Code Block。如下.py文件編譯完成后會生成三個PyCodeObject,一個對應整個.py文件一個對應Class A,一個對應def Fun。實際這三個code對象是嵌套的,后兩個code對象位于第一個code對象的co_consts屬性中。其實,字節(jié)碼位于co_code中。
class A: pass def Fun(): pass a = A() Fun()
pyc文件包括三部分:
(1)四字節(jié)的Magic int,表示pyc版本信息
(2)四字節(jié)的int,是pyc產(chǎn)生時間,若與py文件時間不同會重新生成
(3)序列化了的PyCodeObject對象。
3、pyc文件的生成
寫入pyc文件的函數(shù)包括以下幾個步驟:
PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION); // 寫入版本信息 PyMarshal_WriteLongToFile(0L, fp, Py_MARSHAL_VERSION); // 寫入時間信息 PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION); // 寫入PyCodeObject對象
關鍵在于code對象的寫入:
{
WFILE wf;
wf.fp = fp;
……
w_object(x, &wf);
}
用到了一個WFILE結(jié)構(gòu)體,可以認為是對FILE *fp 的一個封裝:
typedef struct {
FILE *fp;
int error;
int depth;
PyObject *strings; // 存儲字符串,寫入時以dict形式,讀出時以list形式
} WFILE;
關鍵在于w_object()函數(shù):
static void w_object(PyObject *v, WFILE *p){
if (v == NULL) ……
else if (PyInt_CheckExact(v)) ……
else if (PyFloat_CheckExact(v)) ……
else if (PyString_CheckExact(v)) ……
else if (PyList_CheckExact(v)) ……
}
w_code實質(zhì)為根據(jù)不同的對象類型選取不同的策略,例如tuple對象:
else if (PyTuple_CheckExact(v)) {
w_byte(TYPE_TUPLE, p);
n = PyTuple_Size(v);
W_SIZE(n, p);
for (i = 0; i < n; i++)
w_object(PyTuple_GET_ITEM(v, i), p);
而所有類型最終可分解為寫入數(shù)值與寫入字符串兩種操作,涉及以下幾部分:
#define w_byte(c, p) putc((c), (p)->fp) // 用于寫入類型
static void w_long(long x, WFILE *p){ // 用于寫入數(shù)字
w_byte((char)( x& 0xff), p); // 實質(zhì)為用四個字節(jié)存儲一個數(shù)字
w_byte((char)((x>> 8) & 0xff), p);
w_byte((char)((x>>16) & 0xff), p);
w_byte((char)((x>>24) & 0xff), p);
}
static void w_string(char *s, int n, WFILE *p){ //用于寫入字符串
fwrite(s, 1, n, p->fp);
}
由于序列化寫入文件后丟失了結(jié)構(gòu)信息,故寫入每個對象時寫入類型信息w_byte:
#define TYPE_INT 'i'
#define TYPE_LIST'['
#define TYPE_DICT'{'
#define TYPE_CODE'c'
由于Python皆對象,w_object(PyObject*)便可針對不同類型選取不同寫入方法,不斷細分,最終分解為PyInt_Object或PyString_Object,利用w_long或w_string寫入。
數(shù)字比較簡單:
else if (PyInt_CheckExact(v)) {
w_byte(TYPE_INT, p);
w_long(x, p);
}
字符串則比較復雜:
else if (PyString_CheckExact(v)) {
if (p->strings && PyString_CHECK_INTERNED(v)) {
PyObject *o = PyDict_GetItem(p->strings, v); // 獲取在strings中的序號
if (o) { // inter對象的非首次寫入
long w = PyInt_AsLong(o);
w_byte(TYPE_STRINGREF, p);
w_long(w, p);
goto exit;
}
else { // intern對象的首次寫入
int ok;
ok = o && PyDict_SetItem(p->strings, v, o) >= 0;
Py_XDECREF(o);
w_byte(TYPE_INTERNED, p);
}
}
else { // 寫入普通string
w_byte(TYPE_STRING, p);
}
n = PyString_GET_SIZE(v);
W_SIZE(n, p);
w_string(PyString_AS_STRING(v), n, p);
}
(1)若寫入普通字符串,寫入字符串類型信息"S",然后寫入字符串長度及string值。
(2)若寫入inter字符串,先到WFILE的strings中查找:
(a)若找到,則寫入引用類型信息"R",然后寫入序號
(b)若未找到,創(chuàng)建對象放入strings,并寫入intern類型信息"t",然后寫入字符串長度及string值。
若依次寫入"efei"、"snow"、"efei",則會如下:

從pyc文件讀入時,依靠list,那么序號就可以利用上了。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注本站的更多內(nèi)容!
版權(quán)聲明:本站文章來源標注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請保持原文完整并注明來源及原文鏈接。禁止復制或仿造本網(wǎng)站,禁止在非maisonbaluchon.cn所屬的服務器上建立鏡像,否則將依法追究法律責任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學習參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請聯(lián)系alex-e#qq.com處理。
關注官方微信