python源码解读_Python源码剖析[16] —— Pyc文件解析
Python源碼剖析[16] —— Pyc文件解析
Python源碼剖析[16]?——?Pyc文件解析
2008-02-28?18:29:55|??分類:?Python?|舉報|字號?訂閱
Python源碼剖析
——Pyc文件解析
本文作者:?Robert?Chen?(search.pythoner@gmail.com?)
1.??????PyCodeObject與Pyc文件
通常認(rèn)為,Python是一種解釋性的語言,但是這種說法是不正確的,實際上,Python在執(zhí)行時,首先會將.py文件中的源代碼編譯成Python的byte?code(字節(jié)碼),然后再由Python?Virtual?Machine來執(zhí)行這些編譯好的byte?code。這種機制的基本思想跟Java,.NET是一致的。然而,Python?Virtual?Machine與Java或.NET的Virtual?Machine不同的是,Python的Virtual?Machine是一種更高級的Virtual?Machine。這里的高級并不是通常意義上的高級,不是說Python的Virtual?Machine比Java或.NET的功能更強大,更拽,而是說和Java或.NET相比,Python的Virtual?Machine距離真實機器的距離更遠(yuǎn)。或者可以這么說,Python的Virtual?Machine是一種抽象層次更高的Virtual?Machine。
我們來考慮下面的Python代碼:
[demo.py]
class?A:
pass
def?Fun():
pass
value?=?1
str?=?“Python”
a?=?A()
Fun()
Python在執(zhí)行CodeObject.py時,首先需要進行的動作就是對其進行編譯,編譯的結(jié)果是什么呢?當(dāng)然有字節(jié)碼,否則Python也就沒辦法在玩下去了。然而除了字節(jié)碼之外,還包含其它一些結(jié)果,這些結(jié)果也是Python運行的時候所必需的。看一下我們的demo.py,用我們的眼睛來解析一下,從這個文件中,我們可以看到,其中包含了一些字符串,一些常量值,還有一些操作。當(dāng)然,Python對操作的處理結(jié)果就是自己碼。那么Python的編譯過程對字符串和常量值的處理結(jié)果是什么呢?實際上,這些在Python源代碼中包含的靜態(tài)的信息都會被Python收集起來,編譯的結(jié)果中包含了字符串,常量值,字節(jié)碼等等在源代碼中出現(xiàn)的一切有用的靜態(tài)信息。而這些信息最終會被存儲在Python運行期的一個對象中,當(dāng)Python運行結(jié)束后,這些信息甚至還會被存儲在一種文件中。這個對象和文件就是我們這章探索的重點:PyCodeObject對象和Pyc文件。
可以說,PyCodeObject就是Python源代碼編譯之后的關(guān)于程序的靜態(tài)信息的集合:
[compile.h]
/*?Bytecode?object?*/
typedef?struct?{
PyObject_HEAD
int?co_argcount;????????/*?#arguments,?except?*args?*/
int?co_nlocals;?????/*?#local?variables?*/
int?co_stacksize;???????/*?#entries?needed?for?evaluation?stack?*/
int?co_flags;???????/*?CO_...,?see?below?*/
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?doesn't?count?for?hash/cmp?*/
PyObject?*co_filename;??/*?string?(where?it?was?loaded?from)?*/
PyObject?*co_name;??????/*?string?(name,?for?reference)?*/
int?co_firstlineno;?????/*?first?source?line?number?*/
PyObject?*co_lnotab;????/*?string?(encoding?addrlineno?mapping)?*/
}?PyCodeObject;
在對Python源代碼進行編譯的時候,對于一段Code(Code?Block),會創(chuàng)建一個PyCodeObject與這段Code對應(yīng)。那么如何確定多少代碼算是一個Code?Block呢,事實上,當(dāng)進入新的作用域時,就開始了新的一段Code。也就是說,對于下面的這一段Python源代碼:
[CodeObject.py]
class?A:
pass
def?Fun():
pass
a?=?A()
Fun()
在Python編譯完成后,一共會創(chuàng)建3個PyCodeObject對象,一個是對應(yīng)CodeObject.py的,一個是對應(yīng)class?A這段Code(作用域),而最后一個是對應(yīng)def?Fun這段Code的。每一個PyCodeObject對象中都包含了每一個代碼塊經(jīng)過編譯后得到的byte?code。但是不幸的是,Python在執(zhí)行完這些byte?code后,會銷毀PyCodeObject,所以下次再次執(zhí)行這個.py文件時,Python需要重新編譯源代碼,創(chuàng)建三個PyCodeObject,然后執(zhí)行byte?code。
很不爽,對不對?Python應(yīng)該提供一種機制,保存編譯的中間結(jié)果,即byte?code,或者更準(zhǔn)確地說,保存PyCodeObject。事實上,Python確實提供了這樣一種機制——Pyc文件。
Python中的pyc文件正是保存PyCodeObject的關(guān)鍵所在,我們對Python解釋器的分析就從pyc文件,從pyc文件的格式開始。
在分析pyc的文件格式之前,我們先來看看如何產(chǎn)生pyc文件。在執(zhí)行一個.py文件中的源代碼之后,Python并不會自動生成與該.py文件對應(yīng)的.pyc文件。我們需要自己觸發(fā)Python來創(chuàng)建pyc文件。下面我們提供一種使Python創(chuàng)建pyc文件的方法,其實很簡單,就是利用Python的import機制。
在Python運行的過程中,如果碰到import?abc,這樣的語句,那么Python將到設(shè)定好的path中尋找abc.pyc或abc.dll文件,如果沒有這些文件,而只是發(fā)現(xiàn)了abc.py,那么Python會首先將abc.py編譯成相應(yīng)的PyCodeObject的中間結(jié)果,然后創(chuàng)建abc.pyc文件,并將中間結(jié)果寫入該文件。接下來,Python才會對abc.pyc文件進行一個import的動作,實際上也就是將abc.pyc文件中的PyCodeObject重新在內(nèi)存中復(fù)制出來。了解了這個過程,我們很容易利用下面所示的generator.py來創(chuàng)建上面那段代碼(CodeObjectt.py)對應(yīng)的pyc文件了。
generator.py
CodeObject.py
import?test
print?"Done"
class?A:
pass
def?Fun():
pass
a?=?A()
Fun()
圖1所示的是Python產(chǎn)生的pyc文件:
可以看到,pyc是一個二進制文件,那么Python如何解釋這一堆看上去毫無意義的字節(jié)流就至關(guān)重要了。這也就是pyc文件的格式。
要了解pyc文件的格式,首先我們必須要清楚PyCodeObject中每一個域都表示什么含義,這一點是無論如何不能繞過去的。
Field
Content
co_argcount
Code?Block的參數(shù)的個數(shù),比如說一個函數(shù)的參數(shù)
co_nlocals
Code?Block中局部變量的個數(shù)
co_stacksize
執(zhí)行該段Code?Block需要的棧空間
co_flags
N/A
co_code
Code?Block編譯所得的byte?code。以PyStringObject的形式存在
co_consts
PyTupleObject對象,保存該Block中的常量
co_names
PyTupleObject對象,保存該Block中的所有符號
co_varnames
N/A
co_freevars
N/A
co_cellvars
N/A
co_filename
Code?Block所對應(yīng)的.py文件的完整路徑
co_name
Code?Block的名字,通常是函數(shù)名或類名
co_firstlineno
Code?Block在對應(yīng)的.py文件中的起始行
co_lnotab
byte?code與.py文件中source?code行號的對應(yīng)關(guān)系,以PyStringObject的形式存在
需要說明一下的是co_lnotab域。在Python2.3以前,有一個byte?code,喚做SET_LINENO,這個byte?code會記錄.py文件中source?code的位置信息,這個信息對于調(diào)試和顯示異常信息都有用。但是,從Python2.3之后,Python在編譯時不會再產(chǎn)生這個byte?code,相應(yīng)的,Python在編譯時,將這個信息記錄到了co_lnotab中。
co_lnotab中的byte?code和source?code的對應(yīng)信息是以unsigned?bytes的數(shù)組形式存在的,數(shù)組的形式可以看作(byte?code在co_code中位置增量,代碼行數(shù)增量)形式的一個list。比如對于下面的例子:
Byte?code在co_code中的偏移
.py文件中源代碼的行數(shù)
0
1
6
2
50
7
這里有一個小小的技巧,Python不會直接記錄這些信息,相反,它會記錄這些信息間的增量值,所以,對應(yīng)的co_lnotab就應(yīng)該是:0,1,?6,1,?44,5。
2.??????Pyc文件的生成
前面我們提到,Python在import時,如果沒有找到相應(yīng)的pyc文件或dll文件,就會在py文件的基礎(chǔ)上自動創(chuàng)建pyc文件。那么,要想了解pyc的格式到底是什么樣的,我們只需要考察Python在將編譯得到的PyCodeObject寫入到pyc文件中時到底進行了怎樣的動作就可以了。下面的函數(shù)就是我們的切入點:
[import.c]
static?void?write_compiled_module(PyCodeObject?*co,?char?*cpathname,?long?mtime)
{
FILE?*fp;
fp?=?open_exclusive(cpathname);
PyMarshal_WriteLongToFile(pyc_magic,?fp,?Py_MARSHAL_VERSION);
/*?First?write?a?0?for?mtime?*/
PyMarshal_WriteLongToFile(0L,?fp,?Py_MARSHAL_VERSION);
PyMarshal_WriteObjectToFile((PyObject?*)co,?fp,?Py_MARSHAL_VERSION);
/*?Now?write?the?true?mtime?*/
fseek(fp,?4L,?0);
PyMarshal_WriteLongToFile(mtime,?fp,?Py_MARSHAL_VERSION);
fflush(fp);
fclose(fp);
}
這里的cpathname當(dāng)然是pyc文件的絕對路徑。首先我們看到會將pyc_magic這個值寫入到文件的開頭。實際上,pyc?_magic對應(yīng)一個MAGIC的值。MAGIC是用來保證Python兼容性的一個措施。比如說要防止Python2.4的運行環(huán)境加載由Python1.5產(chǎn)生的pyc文件,那么只需要將Python2.4和Python1.5的MAGIC設(shè)為不同的值就可以了。Python在加載pyc文件時會首先檢查這個MAGIC值,從而拒絕加載不兼容的pyc文件。那么pyc文件為什么會不兼容了,一個最主要的原因是byte?code的變化,由于Python一直在不斷地改進,有一些byte?code退出了歷史舞臺,比如上面提到的SET_LINENO;或者由于一些新的語法特性會加入新的byte?code,這些都會導(dǎo)致Python的不兼容問題。
pyc文件的寫入動作最后會集中到下面所示的幾個函數(shù)中(這里假設(shè)代碼只處理寫入到文件,即p->fp是有效的。因此代碼有刪減,另有一個w_short未列出。缺失部分,請參考Python源代碼):
[marshal.c]
typedef?struct?{
FILE?*fp;
int?error;
int?depth;
PyObject?*strings;?/*?dict?on?marshal,?list?on?unmarshal?*/
}?WFILE;
#define?w_byte(c,?p)?putc((c),?(p)->fp)
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);
}
static?void?w_string(char?*s,?int?n,?WFILE?*p)
{
fwrite(s,?1,?n,?p->fp);
}
在調(diào)用PyMarshal_WriteLongToFile時,會直接調(diào)用w_long,但是在調(diào)用PyMarshal_WriteObjectToFile時,還會通過一個間接的函數(shù):w_object。需要特別注意的是PyMarshal_WriteObjectToFile的第一個參數(shù),這個參數(shù)正是Python編譯出來的PyCodeObject對象。
w_object的代碼非常長,這里就不全部列出。其實w_object的邏輯非常簡單,就是對應(yīng)不同的對象,比如string,int,list等,會有不同的寫的動作,然而其最終目的都是通過最基本的w_long或w_string將整個PyCodeObject寫入到pyc文件中。
對于PyCodeObject,很顯然,會遍歷PyCodeObject中的所有域,將這些域依次寫入:
[marshal.c]
static?void?w_object(PyObject?*v,?WFILE?*p)
{
……
else?if?(PyCode_Check(v))
{
PyCodeObject?*co?=?(PyCodeObject?*)v;
w_byte(TYPE_CODE,?p);
w_long(co->co_argcount,?p);
w_long(co->co_nlocals,?p);
w_long(co->co_stacksize,?p);
w_long(co->co_flags,?p);
w_object(co->co_code,?p);
w_object(co->co_consts,?p);
w_object(co->co_names,?p);
w_object(co->co_varnames,?p);
w_object(co->co_freevars,?p);
w_object(co->co_cellvars,?p);
w_object(co->co_filename,?p);
w_object(co->co_name,?p);
w_long(co->co_firstlineno,?p);
w_object(co->co_lnotab,?p);
}
……
}
而對于一個PyListObject對象,想象一下會有什么動作?沒錯,還是遍歷!!!:
[w_object()?in?marshal.c]
……
else?if?(PyList_Check(v))
{
w_byte(TYPE_LIST,?p);
n?=?PyList_GET_SIZE(v);
w_long((long)n,?p);
for?(i?=?0;?i?
{
w_object(PyList_GET_ITEM(v,?i),?p);
}
}
……
而如果是PyIntObject,嗯,那太簡單了,幾乎沒有什么可說的:
[w_object()?in?marshal.c]
……
else?if?(PyInt_Check(v))
{
w_byte(TYPE_INT,?p);
w_long(x,?p);
}
……
有沒有注意到TYPE_LIST,TYPE_CODE,TYPE_INT這樣的標(biāo)志?pyc文件正是利用這些標(biāo)志來表示一個新的對象的開始,當(dāng)加載pyc文件時,加載器才能知道在什么時候應(yīng)該進行什么樣的加載動作。這些標(biāo)志同樣也是在import.c中定義的:
[import.c]
#define?TYPE_NULL???'0'
#define?TYPE_NONE???'N'
。。。。。。
#define?TYPE_INT????'i'
#define?TYPE_STRING?'s'
#define?TYPE_INTERNED???'t'
#define?TYPE_STRINGREF??'R'
#define?TYPE_TUPLE??'('
#define?TYPE_LIST???'['
#define?TYPE_CODE???'c'
到了這里,可以看到,Python對于中間結(jié)果的導(dǎo)出實際是不復(fù)雜的。實際上在write的動作中,不論面臨PyCodeObject還是PyListObject這些復(fù)雜對象,最后都會歸結(jié)為簡單的兩種形式,一個是對數(shù)值的寫入,一個是對字符串的寫入。上面其實我們已經(jīng)看到了對數(shù)值的寫入過程。在寫入字符串時,有一套比較復(fù)雜的機制。在了解字符串的寫入機制前,我們首先需要了解一個寫入過程中關(guān)鍵的結(jié)構(gòu)體WFILE(有刪節(jié)):
[marshal.c]
typedef?struct?{
FILE?*fp;
int?error;
int?depth;
PyObject?*strings;?/*?dict?on?marshal,?list?on?unmarshal?*/
}?WFILE;
這里我們也只考慮fp有效,即寫入到文件,的情況。WFILE可以看作是一個對FILE*的簡單包裝,但是在WFILE里,出現(xiàn)了一個奇特的strings域。這個域是在pyc文件中寫入或讀出字符串的關(guān)鍵所在,當(dāng)向pyc中寫入時,string會是一個PyDictObject對象;而從pyc中讀出時,string則會是一個PyListObject對象。
[marshal.c]
void?PyMarshal_WriteObjectToFile(PyObject?*x,?FILE?*fp,?int?version)
{
WFILE?wf;
wf.fp?=?fp;
wf.error?=?0;
wf.depth?=?0;
wf.strings?=?(version?>?0)???PyDict_New()?:?NULL;
w_object(x,?&wf);
}
可以看到,strings在真正開始寫入之前,就已經(jīng)被創(chuàng)建了。在w_object中對于字符串的處理部分,我們可以看到對strings的使用:
[w_object()?in?marshal.c]
……
else?if?(PyString_Check(v))
{
if?(p->strings?&&?PyString_CHECK_INTERNED(v))
{
PyObject?*o?=?PyDict_GetItem(p->strings,?v);
if?(o)
{
long?w?=?PyInt_AsLong(o);
w_byte(TYPE_STRINGREF,?p);
w_long(w,?p);
goto?exit;
}
else
{
o?=?PyInt_FromLong(PyDict_Size(p->strings));
PyDict_SetItem(p->strings,?v,?o);
Py_DECREF(o);
w_byte(TYPE_INTERNED,?p);
}
}
else
{
w_byte(TYPE_STRING,?p);
}
n?=?PyString_GET_SIZE(v);
w_long((long)n,?p);
w_string(PyString_AS_STRING(v),?n,?p);
}
……
真正有趣的事發(fā)生在這個字符串是一個需要被進行INTERN操作的字符串時。可以看到,WFILE的strings域?qū)嶋H上是一個從string映射到int的一個PyDictObject對象。這個int值是什么呢,這個int值是表示對應(yīng)的string是第幾個被加入到WFILE.strings中的字符串。
這個int值看上去似乎沒有必要,記錄一個string被加入到WFILE.strings中的序號有什么意義呢?好,讓我們來考慮下面的情形:
假設(shè)我們需要向pyc文件中寫入三個string:”Jython”,?“Ruby”,?“Jython”,而且這三個string都需要被進行INTERN操作。對于前兩個string,沒有任何問題,閉著眼睛寫入就是了。完成了前兩個string的寫入后,WFILE.strings與pyc文件的情況如圖2所示:
在寫入第三個字符串的時候,麻煩來了。對于這個“Jython”,我們應(yīng)該怎么處理呢?
是按照上兩個string一樣嗎?如果這樣的話,那么寫入后,WFILE.strings和pyc的情況如圖3所示:
我們可以不管WFILE.strings怎么樣了,但是一看pyc文件,我們就知道,問題來了。在pyc文件中,出現(xiàn)了重復(fù)的內(nèi)容,關(guān)于“Jython”的信息重復(fù)了兩次,這會引起什么麻煩呢?想象一下在python代碼中,我們創(chuàng)建了一個button,在此之后,多次使用了button,這樣,在代碼中,“button”將出現(xiàn)多次。想象一下吧,我們的pyc文件會變得多么臃腫,而其中充斥的只是毫無價值的冗余信息。如果你是Guido,你能忍受這樣的設(shè)計嗎?當(dāng)然不能!!于是Guido給了我們TYPE_STRINGREF這個東西。在解析pyc文件時,這個標(biāo)志表明后面的一個數(shù)值表示了一個索引值,根據(jù)這個索引值到WFILE.strings中去查找,就能找到需要的string了。
有了TYPE_STRINGREF,我們的pyc文件就能變得苗條了,如圖4所示:
看一下加載pyc文件的過程,我們就能對這個機制更加地明了了。前面我們提到,在讀入pyc文件時,WFILE.strings是一個PyListObject對象,所以在讀入前兩個字符串后,WFILE.strings的情形如圖5所示:
在加載緊接著的(R,0)時,因為解析到是一個TYPE_STRINGREF標(biāo)志,所以直接以標(biāo)志后面的數(shù)值0位索引訪問WFILE.strings,立刻可得到字符串“Jython”。
3.??????一個PyCodeObject,多個PyCodeObject?
到了這里,關(guān)于PyCodeObject與pyc文件,我們只剩下最后一個有趣的話題了。還記得前面那個test.py嗎?我們說那段簡單的什么都做不了的python代碼就要產(chǎn)生三個PyCodeObject。而在write_compiled_module中我們又親眼看到,Python運行環(huán)境只會對一個PyCodeObject對象調(diào)用PyMarshal_WriteObjectToFile操作。剎那間,我們竟然看到了兩個遺失的PyCodeObject對象。
Python顯然不會犯這樣低級的錯誤,想象一下,如果你是Guido,這個問題該如何解決?首先我們會假想,有兩個PyCodeObject對象一定是包含在另一個PyCodeObject中的。沒錯,確實如此,還記得我們最開始指出的Python是如何確定一個Code?Block的嗎?對嘍,就是作用域。仔細(xì)看一下test.py,你會發(fā)現(xiàn)作用域呈現(xiàn)出一種嵌套的結(jié)構(gòu),這種結(jié)構(gòu)也正是PyCodeObject對象之間的結(jié)構(gòu)。所以到現(xiàn)在清楚了,與Fun和A對應(yīng)得PyCodeObject對象一定是包含在與全局作用域?qū)?yīng)的PyCodeObject對象中的,而PyCodeObject結(jié)構(gòu)中的co_consts域正是這兩個PyCodeObject對象的藏身之處,如圖6所示:
在對一個PyCodeObject對象進行寫入到pyc文件的操作時,如果碰到它包含的另一個PyCodeObject對象,那么就會遞歸地執(zhí)行寫入PyCodeObject對象的操作。如此下去,最終所有的PyCodeObject對象都會被寫入到pyc文件中去。而且pyc文件中的PyCodeObject對象也是以一種嵌套的關(guān)系聯(lián)系在一起的。
4.??????Python字節(jié)碼
Python源代碼在執(zhí)行前會被編譯為Python的byte?code,Python的執(zhí)行引擎就是根據(jù)這些byte?code來進行一系列的操作,從而完成對Python程序的執(zhí)行。在Python2.4.1中,一共定義了103條byte?code:
[opcode.h]
#define?STOP_CODE???0
#define?POP_TOP?????1
#define?ROT_TWO?????2
……
#define?CALL_FUNCTION_KW???????????141
#define?CALL_FUNCTION_VAR_KW???????142
#define?EXTENDED_ARG??143
所有這些字節(jié)碼的操作含義在Python自帶的文檔中有專門的一頁進行描述,當(dāng)然,也可以到下面的網(wǎng)址察看:http://docs.python.org/lib/bytecodes.html。
細(xì)心的你一定發(fā)現(xiàn)了,byte?code的編碼卻到了143。沒錯,Python2.4.1中byte?code的編碼并沒有按順序增長,比如編碼為5的ROT_FOUR之后就是編碼為9的NOP。這可能是歷史遺留下來的,你知道,在咱們這行,歷史問題不是什么好東西,搞得現(xiàn)在還有許多人不得不很郁悶地面對MFC?:)
Python的143條byte?code中,有一部分是需要參數(shù)的,另一部分是沒有參數(shù)的。所有需要參數(shù)的byte?code的編碼都大于或等于90。Python中提供了專門的宏來判斷一條byte?code是否需要參數(shù):
[opcode.h]
#define?HAS_ARG(op)?((op)?>=?HAVE_ARGUMENT)
好了,到了現(xiàn)在,關(guān)于PyCodeObject和pyc文件的一切我們都已了如指掌了,關(guān)于Python的現(xiàn)在我們可以做一些非常有趣的事了。呃,在我看來,最有趣的事莫過于自己寫一個pyc文件的解析器。沒錯,利用我們現(xiàn)在所知道的一切,我們真的可以這么做了。圖7展現(xiàn)的是對本章前面的那個test.py的解析結(jié)果:
更進一步,我們還可以解析byte?code。前面我們已經(jīng)知道,Python在生成pyc文件時,會將PyCodeObject對象中的byte?code也寫入到pyc文件中,而且這個pyc文件中還記錄了每一條byte?code與Python源代碼的對應(yīng)關(guān)系,嗯,就是那個co_lnotab啦。假如現(xiàn)在我們知道了byte?code在co_code中的偏移地址,那么與這條byte?code對應(yīng)的Python源代碼的位置可以通過下面的算法得到(Python偽代碼):
lineno?=?addr?=?0
for?addr_incr,?line_incr?in?c_lnotab:
addr?+=?addr_incr
if?addr?>?A:
return?lineno
lineno?+=?line_incr
下面是對一段Python源代碼反編譯為byte?code的結(jié)果,這個結(jié)果也將作為下一章對Python執(zhí)行引擎的分析的開始:
i?=?1
#???LOAD_CONST???0
#???STORE_NAME???0
s?=?"Python"
#???LOAD_CONST???1
#???STORE_NAME???1
d?=?{}
#???BUILD_MAP???0
#???STORE_NAME???2
l?=?[]
#???BUILD_LIST???0
#???STORE_NAME???3
#???LOAD_CONST???2
#???RETURN_VALUE???none
再往前想一想,從現(xiàn)在到達(dá)的地方出發(fā),實際上我們就可以做出一個Python的執(zhí)行引擎了,哇,這是多么激動人心的事啊。遙遠(yuǎn)的天空,一抹朝陽,緩緩升起了……
事實上,Python標(biāo)準(zhǔn)庫中提供了對python進行反編譯的工具dis,利用這個工具,可以很容易地得到我們在這里得到的結(jié)果,當(dāng)然,還要更詳細(xì)一些,圖8展示了利用dis工具對CodeObject.py進行反編譯的結(jié)果:
在圖8顯示的結(jié)果中,最左面一列顯示的是CodeObject.py中源代碼的行數(shù),左起第二列顯示的是當(dāng)前的字節(jié)碼指令在co_code中的偏移位置。
在以后的分析中,我們大部分將采用dis工具的反編譯結(jié)果,在有些特殊情況下會使用我們自己的反編譯結(jié)果。
原文鏈接:http://blog.163.com/dailongquan@126/blog/static/52600902200812862955306/
總結(jié)
以上是生活随笔為你收集整理的python源码解读_Python源码剖析[16] —— Pyc文件解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SQLServer LIKE 通配符
- 下一篇: 锁优化