源代碼就是程序員寫的人類能夠看懂的代碼。我們編寫的程序文件的執(zhí)行需要兩步:1强缘、編譯器將源碼編譯成二進(jìn)制文件,文件里是字節(jié)碼指令,字節(jié)碼會存儲在 PyCodeObject 對象中抛蚤;2、虛擬機(jī)運(yùn)行二進(jìn)制文件寻狂,在運(yùn)行時虛擬機(jī)會創(chuàng)建字節(jié)碼執(zhí)行的上下文環(huán)境岁经,也就是下文提到的各種棧,使用 PyFrameObject 表示運(yùn)行時的 “調(diào)用椛呷” 缀壤。Python 中這兩步是合在一起的樊拓,由 Python 解釋器完成。實(shí)際上 Python 解釋器由編譯器和虛擬機(jī)組成诉位,將源文件編譯為二進(jìn)制文件這步由編譯器完成骑脱,運(yùn)行二進(jìn)制文件這步由虛擬機(jī)來完成的,運(yùn)行完成后可以看到二進(jìn)制文件苍糠。Java 中這兩步是分開的叁丧,先編譯成二進(jìn)制文件,再由 JVM(Java 虛擬機(jī))執(zhí)行岳瞭。其實(shí)二者流程差不多拥娄,側(cè)重不同。
Python 文件在運(yùn)行之后會在同一個目錄中出現(xiàn) .pyc 文件瞳筏,這是上面提到的緩存二進(jìn)制文件稚瘾,它有助于提高文件再次運(yùn)行的效率,緩存文件通常保存在 __pycache__ 目錄下姚炕,緩存文件中的數(shù)據(jù)就是只有機(jī)器能夠識別的字節(jié)碼摊欠,使用編輯器打開文件我們也看不懂。
Python 與大多數(shù)解釋型語言一樣柱宦,解釋器在運(yùn)行程序時首先將源代碼編譯為一組虛擬機(jī)指令些椒,并且 Python 解釋器是針對相應(yīng)的虛擬機(jī)實(shí)現(xiàn)的。這種中間格式的虛擬機(jī)指令被稱為 “字節(jié)碼” 掸刊,也就是說 .pyc 這個二進(jìn)制文件中存儲的是字節(jié)碼指令免糕,這些指令只有對應(yīng)的 Python 虛擬機(jī)可以運(yùn)行。Python 被稱為解釋型語言忧侧,其中一個原因是如上所述的程序運(yùn)行時源代碼被轉(zhuǎn)換成字節(jié)碼指令石窑。
最常用的 Python 解釋器是 CPython ,我們說 "Python 解釋器" 時默認(rèn)就是指 CPython 蚓炬,它使用三種類型的棧:
調(diào)用棧 call stack 松逊、數(shù)據(jù)棧 data stack 、塊棧 block stack調(diào)用棧 call stack 是運(yùn)行 Python 程序的主要結(jié)構(gòu)试吁。它為每個當(dāng)前活動的函數(shù)調(diào)用創(chuàng)建一個東西 —— “幀 frame”棺棵,也叫棧幀,調(diào)用棧的棧底是程序的入口點(diǎn)熄捍。每個函數(shù)調(diào)用推送一個新的幀到調(diào)用棧烛恤,每當(dāng)函數(shù)調(diào)用返回后,這個幀被銷毀余耽。
在每個幀中缚柏,有一個數(shù)據(jù)棧 data stack(也稱為計算棧 evaluation stack)。數(shù)據(jù)棧就是 Python 函數(shù)運(yùn)行的地方碟贾,運(yùn)行的 Python 代碼大多數(shù)是由推入到這個棧中的內(nèi)容組成的币喧,解釋器操作它們,然后在函數(shù)返回后銷毀它們干发。
在每個幀中,還有一個塊棧 block stack 史翘。它被 Python 用于跟蹤某些類型的控制結(jié)構(gòu):循環(huán)、try / except 塊以及 with 塊琼讽,將語句塊全部推入到塊棧中必峰,當(dāng)退出這些控制結(jié)構(gòu)時,塊棧被銷毀钻蹬。這將幫助 Python 了解任意給定時刻哪個塊是活動的肝匆,例如 continue 或者 break 語句可能影響塊的運(yùn)行。
In [34]: import dis
In [35]: def hello():
...: print('Hello World')
...:
In [36]: dis.dis(hello)
2 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Hello World')
4 CALL_FUNCTION 1
6 POP_TOP
8 LOAD_CONST 0 (None)
10 RETURN_VALUE