![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iZwMDOzUWZwITZkVTMyMzMjlTN2gTZkBjYkNzNiRjN48CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
最終 Python 的執行過程,實際上就是 Python VM 對 ByteCode 的解釋過程。程式的三種基本結構:
- 順序
- 條件
- 循環
數學上已經證明,這三種結構可以表述任意邏輯。Python 也不例外,ByteCode 中也必定實作了這三種結構。
順序執行
順序執行是最樸素的一種,CodeObject 就是 ByteCode 在 Frame 中的表示,我們可以通過反彙編對應的 pyc 檔案,獲得 “彙編代碼”,CodeObject、ByteCode、pyc 檔案這三種形式表達的是同一個邏輯。
CodeObject 的實作與 C 語言的 char [] 數組類似,一個個位元組碼順序排開,Python VM 中通過一個超大的 while 循環,逐個讀取這些指令,并一一執行。
以最簡單的兩行代碼為例:
a
預先安裝 xdis 子產品後,使用可以先将代碼編譯為 pyc 檔案,再反彙編得到彙編代碼。那麼位元組碼等效于下面:
剛好實踐一下 65 節的内容。
正式的 Python 代碼不太可能這麼短,對應的位元組碼也會長的多,但它們沒有本質的差別。
下面就需要 Python VM 出場了,首先通過 1、2 指令,在局部變量字典中建立了 "a" -> 1 的關聯,然後将讀取的指針 + 1,讀取下一條指令,建立 "b" -> 2 的關聯。最後的加載的常量 None 和 RETURN_VALUE 指令是 Python 自動為 module 增加的傳回值 None,希望你還記得 Python 将 py 檔案處理為 module 這回事。
Python 通過不斷将讀取指針調整到相鄰的下一個位置,自然的實作了順序執行。
《源碼解析》對這一部分展開較多,但在邏輯上反而簡單的多。
條件執行
Python 中的條件語句主要通過 if 語句實作, 完整的 if 語句有以下幾部分:
- 條件判斷語句;
- True 時執行的指令;
- False 時執行的指令;
其中條件判斷語句不再展開,它的結果隻可能在運作棧中隻會留下一個 boolean 或者與之等效的對象。
條件執行的 True 和 False 基本上可以認為是兩個順序執行的單元,當然這塊可以嵌套其他更複雜的結構,我們暫時不考慮它們。轉換成流程圖是這樣:
我們知道 ByteCode 實際上是一個連續的線形序列,不能像流程圖中這樣出現兩個并列的代碼塊,我們來做一點位置調整,把它們擠壓到一條線上:
如果你仔細觀察,上面的圖實際上與最初的流程圖是等效的,隻是對位置稍作調整。
好,下面讓我們看一下簡單 if 語句是如何實作的:
if
下面是與之對應的彙編代碼:
#
注意:
- 0 加載待判斷的 boolean 對象到運作棧,這裡我直接指定為 True;
- 2 根據棧中(也就是上一步加載的對象),如果為 False 就跳轉到标号 10,否則什麼也做,繼續下一指令;
- 4、6 實作了 a = 1;
- 8 跳轉出 if 語句,這是一個無條件跳轉,隻要執行到 8 就一定會跳轉到 14,相當于 C 中的 goto 語句;
- 10、12 類似,實作了 a = 2;
- 14、16 已經在 if 語句之外,是 module 的傳回值。
讓我們把彙編的路徑繪制出來:
為了便于識别,我将彙編代碼的路徑和流程圖放在一起,并且将:
- 對應單元間隔着色,
- 跳轉路徑與對應跳轉語句着色。
我相信你可以一眼看出彙編代碼是如何與流程圖對應起來了!
總結
從位元組碼的角度看, Python VM 在相當大程度上可以認為是一個 “軟 CPU”,基本預計的實作與硬體 CPU 的彙編如出一轍。今天一口氣介紹了兩種基本執行結構。下一步我們要了解循環的實作。
在 x86 等常見 CPU 架構下,循環是通過對 “條件跳轉” 指令的精巧安排實作的,很少在 CPU 層面實作專門用于循環的機器指令,Python 作為一種進階抽象語言,它的特殊性在循環的實作上展現出來了,它采用了一種更抽象的實作方式。
讓我們拭目以待!
關注公衆号 “江川Go”,了解程式員的燒腦日常。