Python 的底層架構

Python 底層架構


當您寫了一段 Python 的原始程式碼按下 Enter 鍵執行出結果後,您是否會好奇您寫的程式是如何被電腦認識且執行的呢?畢竟電腦只認得 0 與 1 兩個符號,而您寫的 Python 程式是英文字母組成的。這中間是如何從英文字母,轉換成 0 與 1 的呢?

Python 的三階段變化:原始碼、位元組碼、機械碼

Python 原始程式轉換成 0 與 1 ,需要經過兩階段的變化。第一階段稱為「直譯(Interpretation)」,會把 Python 語言轉換成一種稱為「位元組碼(Byte Code)」的語言。如下圖所示,左手邊是 Python 原始碼;右手邊是它直譯出來的「位元組碼」。

Python 原始碼與位元組碼
第二階段稱為「編譯(Compilation)」,會把「位元組碼」轉換成只用 0 與 1 兩個符號組合而成的「機械碼(Machine Code)」,並丟入「中央處理器(CPU)」執行。這個由機械碼組成的檔案,就是我們所熟知的「執行檔」了。從「原始碼」、「位元組碼」、到「機械碼」的轉換過程,如下圖所示:

原始碼、位元組碼、機械碼

為何要多翻譯一種「位元組碼」?

為什麼 Python 不直接把「原始碼」轉換成「機械碼」,反而要多翻譯一種中間語言「位元組碼(Bytecode)」呢?這完全都是為了能讓您的 Python 程式,執行於 Windows、Mac、Linux…等不同平台,而所做的努力。


雖說全世界的電腦,能懂得符號都相同,都只有「0 與 1」。但各種電腦對於「0 與 1」的解釋,卻是大大不同。某台電腦對於「01100001」這串符號,或許解釋成「在螢幕上印字」,但另一台電腦對於相同的「01100001」,可能是解釋成「把字印到印表機去」。所以您若直接把 Python 原始碼翻譯成某種機器的 0 與 1 的機械碼,或許能正確執行。但相同的機械碼拿到另一台電腦執行,可能就會跑出與您預期不相同的結果。


打個比方您就會懂。假設在場有阿美族、布農族、排灣族三位原住民朋友。他們每個人都能說國語,與自己的族語。現在來了一位外國傳教士,假設他非常厲害,會說英語、國語、以及三族族語。請問他應該直接說阿美族語,還是說國語,才能讓三位原住民朋友一起瞭解?


上面例子的「英語 = 原始碼」,「國語 = 位元組碼」,「三族原住民語 = 各種機器的機械碼」。要讓人類講的話,一口氣讓不同廠牌的電腦瞭解,我們不能選用特定電腦的機械碼溝通。必須把「原始碼」翻譯成所有機器都共通的「位元組碼」,才能讓程式執行於不同品牌的電腦而毫無困難。這就是為什麼 Python 「原始碼」會先翻譯成「位元組碼」,才翻譯成「機械碼」的原因。


有興趣瞭解「位元組碼」語法的朋友,可以參考官方網站「位元組碼」的簡介。網址在此

「直譯器」與「虛擬機」

那是誰負責把「原始碼」翻譯成「位元組碼」?又是誰把「位元組碼」翻譯成各種品牌的「機械碼」呢?負責把「原始碼 → 位元組碼」的軟體,我們稱為「直譯器(Interpreter)」;而把「位元組碼 → 機械碼」的軟體,我們稱為「虛擬機(Virtual Machine)」。


其中「直譯器」又有各種不同的「實作(Implementation)」。就像「汽車」有福特、豐田、日產…不同「品牌」是一樣的。常見的 Python 直譯器實作有:CPython、PyPy、Jython、IronPython、PythonNet。這些直譯器負責的事情都一樣,都是把原始碼翻譯成位元組碼,只是在「特色」與「效能」這兩項互有高下而已。


而常見的「虛擬機」,有 C、JVM、CLR、Mono 這幾種。它們的任務,就是承接直譯器翻譯出來的位元組碼,然後翻譯成各種不同品牌電腦所使用的「機械碼」。這就像各種品牌的汽車,又可以根據使用場合不同而分成:休旅車、吉普車、轎車、客貨兩用車…等等。特定虛擬機常常跟特定直譯器搭配。


比如說,CPython 直譯器常常跟 C 虛擬機搭配,Jython 直譯器常常跟 JVM 虛擬機搭配…等。因為「直譯器」與「虛擬機」是擔任解譯工作的前後手,兩者必須互相瞭解對方的能耐,才能順利的把原始碼,翻譯成 0 與 1 的機械碼。


茲將常見的「直譯器」與「虛擬機」列成下圖,供讀者參考:


常見的「直譯器」與「虛擬機」

各種 Python 直譯器實作的介紹

之前介紹過 Python 直譯器常見的實作有五種,分別是 CPython、PyPy、Jython、IronPython、PythonNet。為了幫助初學者瞭解這些直譯器的優缺點,以及幫助大家選擇適合自己的直譯器,特別寫了這個小節介紹上述五種直譯器實作的不同點。

  • CPython

CPython 如它的名字所示,它是由 C 語言寫成的 Python 直譯器。這也是 Python 官方製作的。如果您想讓自己寫出來的 Python 程式,與其它人寫出來的模組,有最佳的相容性,那 CPython 這個官方推出的直譯器,就是您最好的選擇。


挑選 CPython 作為直譯器還有另外一個好處,就是它可以很輕易地呼叫由 C 語言寫出來的副程式。C 語言在業界以「速度」見長,它寫出來的程式碼通常又小又快。因此有些 Python 程式設計師,會特意挑選 CPython 作為直譯器,目的是想把大量運算的那段程式碼,由 Python 改成 C 語言實作。這樣不但能使用 Python 各種功能強大的外掛,也能得到 C 語言的速度。

  • PyPy

PyPy 是個很有趣的直譯器。它是用另一套更嚴謹的 Python 語言「RPython」寫成的直譯器。也就是說,PyPy 是用「Python 語言寫出來的直譯器,去直譯您寫的 Python 原始碼」。PyPy 這個名字就是由 Python-Python 這個字縮減而來的。


所謂「直譯」這件工作,也不過是把某串字(如: i = 1 ),翻譯成另一串字存檔而已(如把 i = 1 翻譯成: LOAD_CONST 0 (1) 與 STORE_NAME 0 (i) 這兩串字)。這跟文書處理器中的「尋找-取代」功能其實很類似。既然是單純的「尋找-取代」工作,為什麼不可以用 Python 語言來完成呢?PyPy 就是在這樣的想法下誕生了!


RPython 這個語言,是 Python 語言的子集合。其中的「R」是由「Restricted(限制)」這個字而來的。它把原來 Python 中,與「尋找-取代」無關的功能全數拿掉,保持自身的短小精幹,專心做好「直譯」的工作。


PyPy 除了致力於做出與 CPython 翻譯結果盡可能相容的位元組碼,並希望翻譯出來的位元組碼比 CPython 效能更好、更簡短。國外有一篇效能比較報告,PyPy 翻譯出來的位元組碼,其執行效能比 CPython 還要好五倍!有興趣的朋友可以參考這一篇報告


PyPy 有版本之分。如果您想直譯 Python 2 的原始碼,請用「PyPy」;如果想直譯 Python 3 的原始碼,可能就得下載「PyPy3」了。請小心不要下載到與您原始碼不相容的 PyPy 直譯器。

  • Jython

Jython 是一個能把 Python 原始碼,翻譯成 Java 位元組碼(Bytecode)的直譯器。Java 程式執行的流程,也跟 Python 差不多,也使用「原始碼 > 位元組碼 > 機械碼」三段變化。只是這邊的 Java 位元組碼,雖然也叫位元組碼,但是與 Python 位元組碼不一樣的東西。就像同樣都叫做「中正路」,但基隆的中正路,與花蓮中正路的位置、店家,絕對是天差地遠!只是碰巧使用相同的名字罷了。


那為什麼要把 Python 原始碼翻譯成 Java 位元組碼呢?好處有兩個:(1) 後方可以直接使用與 Java 相同的虛擬機「JVM(Java Virtual Machine)」,無須另外實作一個新的。 (2) 變成 Java 位元組碼後,就可以直接叫用 Java 做好的函式庫。所以一旦使用了 Jython 當直譯器,它的虛擬機毫無懸念地使用 JVM。

所以如果您先前有一大堆用 Java 開發的原始碼、函式庫,想與 Python 整合,那您的直譯器 + 虛擬機組合,大概就會選「Jython + JVM」。不過這個組合也有缺點,就是它只支援到 Python 2.7 的語法。目前沒有Jython 版本可以讓 Python 3 語法與 Java 直接整合。這點,我們只能寄望 Jython 開發團隊加油,快點開發出支援 Python 3 語法的 Jython 直譯器了!

  • IronPython

IronPython 可以把 Python 原始碼,翻譯成微軟 .NET 平台的「位元組碼(Bytecode)」,再由微軟自家的 .NET 虛擬機,翻譯成各品牌電腦的機械碼。什麼事情都要搞一套跟別人不一樣標準的微軟,對於「位元組碼」這件事當然不會放過(笑)。既然 Java 有自己的位元組碼語法、Python 也有 Python 的,那微軟搞個自家規格的位元組碼,一點都不意外。這個自家規格的位元組碼,就是赫赫有名的 .NET。


其實 .NET 有好幾種意思,這個名詞可以拿來指稱微軟家的位元組碼,稱為「.NET 位元組碼」;同樣的名詞也能拿來指稱把 .NET 位元組碼翻譯成機械碼的虛擬機,稱為「.NET 虛擬機」。而 .NET 位元組碼 + .NET 虛擬機 + 微軟寫好的 .NET 函式庫,就統稱為「.NET 平台(.NET Platform)」。


微軟當初會搞自家的 .NET 平台其實是有他的道理的。目前微軟手頭上有幾套很出名的程式語言:Visual Basic(簡稱 VB),C#,Visual C++(簡稱 VC++)。這幾套語言的函式庫,本來是各自獨立的。如果您想寫一個功能叫「讀取資料庫」,那就得寫一版 VB 的、一版 C# 的、一版 VC++ 的。這對微軟工程師而言,一樣的東西要用不同語言寫三遍,簡直是種折磨!後來他們就把函式庫寫成 .NET 位元組碼的形式,然後把 VB,C#,VC++,都改成「原始碼 > 位元組碼 > 機械碼」這種三階段格式。如果想使用函式庫,只要把你的原始碼(不管用 VB、C#、還是 VC++ 寫成的),全都翻譯成微軟 .NET 位元組碼,就可以跟以位元組碼寫成的 .NET 函式庫連接了。


而 IronPython 這個直譯器,就是把你的 Python 原始碼,翻譯成微軟家的 .NET 位元組碼的一套軟體。使用 IronPython 後,您搭配的虛擬機沒有別的選擇,只能使用「共同語言執行期虛擬機(Common Language Run-time,以下簡稱 CLR)」這一套。要命的是,這一套 CLR,只能跑在微軟的 Windows 平台,微軟沒有做 Mac、Linux 平台的 CLR 虛擬機,目的是不希望客人離開 Windows 平台。因此,您的 Python 語言若搭配 IronPython,就只能跑在微軟的 Windows 平台上了,這點請您注意一下。


IronPython 的好處有兩個:第一,它可以與微軟的程式開發工具 Visual Studio.NET 無縫整合。讓您在 Visual Studio.NET 裡,除了寫作原本就支援的 VB、C#、VC++ 語言,還能寫作 Python。第二,它可以讓 Python 原始碼翻譯成 .NET 位元組碼後,直接取用微軟幫各位程式師寫的超棒函式庫。舉凡存取資料庫,存取網路…都不用自己寫了!不過 IronPython 跟 Jython 有個一樣的毛病,就是它只支援到 Python 2.7 的語法。想要寫 Python 3,可能還得等等。

  • PythonNet

Python 搭配 IronPython 只能跑在微軟平台上,對於習慣追求跨平台的 Python 程式師而言,簡直是悲劇!後來 Python 團隊發現,有個稱為「Mono」的虛擬機平台,能將微軟家的 .NET 位元組碼,翻譯成 Windows、Mac OS、Linux…等不同平台的機械碼。Mono 並不是由微軟開發的,而是一家稱為 Xamarin 的公司,模仿 .NET 平台,自己摸索實作出來的「位元組碼 → 機械碼」的虛擬機。


該公司不但做到無縫取用微軟的 .NET 函式庫,為三大平台都實作了 Mono 虛擬機,還很好心地做成「開放原始碼專案」,讓任何人想瞭解 Mono 是怎麼做出來的人,都可以在不用支付一毛錢的情況下,看到 Mono 內部的原始碼。


接著就有人想到,如果我來寫個「Python 原始碼 → Mono 位元組碼」的直譯器,那豈不是能讓 Python 接取 .NET 函式庫,還能跑在 Windows、Mac OS、Linux 三大平台上?這個想法最後的成果,就是 PythonNet。PythonNet 能把 Python 原始碼,翻譯成微軟的 .NET 位元組碼,餵給 Mono 虛擬機後,轉換成各品牌機器的機械碼。所以若您決定使用 PythonNet 作為直譯器,那您的虛擬機也沒有選擇,只能使用 Mono。


喔!對了!PythonNet 支援 Python 2 ~Python 3 的語法,這點是不是比 IronPython 厲害多了呢?

以上,我們介紹了常見的 Python 五大直譯器。您現在知道要怎麼選擇適合自己的直譯器了嗎?

各種常見的「位元組碼 → 機械碼」翻譯軟體:虛擬機

翻譯成位元組碼的 Python 程式,想要正確執行,還是得靠各品牌電腦對應的「虛擬機」翻譯成最終機械碼才行!上面有提過,業界常見的虛擬機,有 C、JVM、CLR、Mono 幾種。接下來我們就一一介紹。

  • C 虛擬機

這個虛擬機,雖然冠有 C 之名,但只是說明這個虛擬機是用 C 語言寫成的。「C 虛擬機」本質上會吃「標準的 Python 位元組碼」,然後吐出各平台相對應的「機械碼」。因為能產生「標準的 Python 位元組碼」的直譯器,有 CPython 與 PyPy 兩款,所以若您的直譯器挑選上述兩款,虛擬機大概都是挑「C 虛擬機」沒有意外。

  • JVM

JVM 的全名是「Java Virtual Machine」,翻譯成中文就是「Java 虛擬機」。其實它根本就與 Java 語言使用的虛擬機是同一個!JVM 會吃 Java 位元組碼,吐出各種平台對應的機械碼。因為能把「Python 原始碼 → Java 位元組碼」的直譯器,只有 Jython 一個。所以只要您選擇 Jython 當直譯器,虛擬機大概就不會是 JVM 以外的選擇。

  • CLR

CLR 是「Common Language Run-time」這三個單字的縮寫,中文意思是「共同語言執行期虛擬機」,其實指的就是能把 .NET 位元組碼翻譯成各平台機械碼的軟體。因為不管是 VB、C#、VC++,都會編譯成 .NET 位元組碼,那 .NET 位元組碼自然就是上述三個語言的「共同語言」囉!

CLR 是微軟製作的「 .NET 位元組碼 → 機械碼」虛擬機。而 IronPython 剛好能吐出 .NET 位元組碼,所以 IronPython 常常與 CLR 搭成一組。

  • Mono

Mono 完全遵守微軟的 .NET 位元組碼規格,在這之上,為了能讓 VB、C#、VC++ 跑在 Mac OS、Linux 平台上,Mono 會對 .NET 位元組碼做「一點點」擴充。所以若您的直譯器採用 IronPython,由於它吐出來的 .NET 位元組碼 Mono 能接受,所以也有「IronPython x Mono」這種組合的。不過跟 Mono 虛擬機搭配,最常見的還是 PythonNet。它能翻譯出 Mono 擴充出來的 .NET 位元組碼。所以「PythonNet x Mono」會是最常見的組合。


結論:我該懂得如何稼接「直譯器 x 虛擬機」嗎?

上面看了這麼多「直譯器」與「虛擬機」,您大概會問:「我要懂得如何把這兩者稼接起來的技術嗎?」答案是「不需要」!市面上的 Python 開發軟體,大多都已經幫您搭配好了。以 Python 官網下載的開發環境來說,它是用「CPython x C 虛擬機」這種組合。您所需要知道的,就是瞭解「CPython、PyPy、Jython…」這些是直譯器,「C、JVM、CLR、Mono」這些是虛擬機。當下載一包 Python 開發環境時看到「Jython x JVM」,能不緊張地冷笑兩聲、心裡浮上一句:「原來如此」,那就及格了!


此外,若您的 Python 程式想直接受益於 C 語言函式庫,知道要選用含有 CPython 直譯器的開發環境;想要取用 Java 函式庫,能下載包含 Jython 直譯器的開發套件;以及想與 VB、C#、VC++…這些「微軟牌語言」連動,會選用 IronPython 或 PythonNet,那我在這邊囉囉嗦嗦地長篇大論,就有十分的價值了!

希望經過這樣的講解,將來您看到自己下載的開發環境,寫著「本開發環境採用 PyPy x C 虛擬機」,或「IronPython x CLR」,能了然於胸,甚至於跟 Python 老手侃侃而談!

參考資料:


本文經授權轉載自《Python 的底層架構》,非經原作者同意不得擷取部分或全部轉載。