為什麼有這麼多蟒蛇? Python 實現比較
已發表: 2022-03-11Python是驚人的。
令人驚訝的是,這是一個相當模棱兩可的說法。 我所說的“Python”是什麼意思? 我的意思是 Python 是抽象接口嗎? 我的意思是 CPython,常見的 Python實現(不要與名稱相似的 Cython 混淆)? 還是我的意思完全不同? 也許我是在間接地指代 Jython、IronPython 或 PyPy。 或者,也許我真的走得太遠了,我在談論 RPython 或 RubyPython(它們是非常非常不同的東西)。
雖然上面提到的技術是通用名稱和通用引用的,但其中一些用於完全不同的目的(或者,至少以完全不同的方式運行)。
在我使用 Python 接口的整個過程中,我遇到了大量的 .*ython 工具。 但直到最近我才花時間了解它們是什麼、它們是如何工作的以及它們為什麼是必要的(以它們自己的方式)。
在本教程中,我將從頭開始,逐步介紹各種 Python 實現,最後對 PyPy 進行全面介紹,我相信這是該語言的未來。
這一切都始於對“Python”實際上是什麼的理解。
如果您對機器代碼、虛擬機等有很好的了解,請隨意跳過。
“Python 是解釋的還是編譯的?”
對於 Python 初學者來說,這是一個常見的困惑點。
進行比較時首先要意識到“Python”是一個接口。 有一個關於 Python應該做什麼以及它應該如何表現的規範(與任何接口一樣)。 並且有多種實現(與任何接口一樣)。
要意識到的第二件事是“解釋”和“編譯”是實現的屬性,而不是接口。
所以這個問題本身並不是很好形成的。
也就是說,對於最常見的 Python 實現(CPython:用 C 編寫,通常簡稱為“Python”,如果您不知道我在說什麼,那麼您肯定會使用什麼),答案是:解釋, 有一些編譯。 CPython 將 * Python 源代碼編譯成字節碼,然後解釋這個字節碼,邊執行邊執行。
*注意:這不是傳統意義上的“編譯”。 通常,我們會說“編譯”是採用高級語言並將其轉換為機器代碼。 但它是一種“彙編”。
讓我們更仔細地看一下這個答案,因為它將幫助我們理解文章後面出現的一些概念。
字節碼與機器碼
理解字節碼與機器碼(也稱為本機代碼)之間的區別非常重要,也許最好通過示例來說明:
- C 編譯為機器代碼,然後直接在您的處理器上運行。 每條指令都會指示你的 CPU 移動東西。
- Java 編譯為字節碼,然後在 Java 虛擬機 (JVM) 上運行,JVM 是執行程序的計算機的抽象。 然後,每條指令都由與您的計算機交互的 JVM 處理。
簡而言之:機器碼要快得多,但字節碼更便攜、更安全。
機器碼在不同的機器上看起來不同,但字節碼在所有機器上看起來都是一樣的。 有人可能會說機器代碼已針對您的設置進行了優化。
回到CPython實現,工具鏈流程如下:
- CPython 將您的 Python 源代碼編譯成字節碼。
- 然後在 CPython 虛擬機上執行該字節碼。
替代 VM:Jython、IronPython 等
正如我之前提到的,Python 有幾個實現。 同樣,如前所述,最常見的是 CPython,但為了本比較指南,還應提及其他一些。 這是一個用 C 編寫的 Python 實現,被認為是“默認”實現。
但是其他 Python 實現呢? 其中一個比較突出的是 Jython,它是一種使用 JVM 編寫的 Java 的 Python 實現。 CPython 生成字節碼以在 CPython VM 上運行,而 Jython 生成Java 字節碼以在 JVM 上運行(這與編譯 Java 程序時生成的東西相同)。
“你為什麼要使用替代實現?”,你可能會問。 好吧,一方面,這些不同的 Python 實現與不同的技術堆棧很好地配合。
CPython 使得為您的 Python 代碼編寫 C 擴展非常容易,因為它最終由 C 解釋器執行。 另一方面,Jython 使得使用其他 Java 程序變得非常容易:您可以毫不費力地導入任何Java 類,從您的 Jython 程序中調用和利用您的 Java 類。 (順便說一句:如果您沒有仔細考慮過,這實際上是瘋了。我們現在可以混合和混合不同的語言並將它們全部編譯成相同的內容。(正如 Rostin 所提到的,程序混合 Fortran 和 C 代碼已經存在了一段時間。所以,當然,這不一定是新的。但它仍然很酷。))
例如,這是有效的 Jython 代碼:
[Java HotSpot(TM) 64-Bit Server VM (Apple Inc.)] on java1.6.0_51 >>> from java.util import HashSet >>> s = HashSet(5) >>> s.add("Foo") >>> s.add("Bar") >>> s [Foo, Bar]
IronPython 是另一種流行的 Python 實現,完全用 C# 編寫並針對 .NET 堆棧。 特別是,它運行在您可能稱之為 .NET 虛擬機上,即 Microsoft 的公共語言運行時 (CLR),與 JVM 相當。
您可能會說Jython : Java :: IronPython : C# 。 它們在相同的虛擬機上運行,您可以從 IronPython 代碼中導入 C# 類,從 Jython 代碼中導入 Java 類等。
完全有可能在不接觸非 CPython Python 實現的情況下生存下來。 但是切換也有一些優勢,其中大部分取決於您的技術堆棧。 使用大量基於 JVM 的語言? Jython 可能適合您。 關於 .NET 堆棧? 也許您應該嘗試 IronPython(也許您已經嘗試過)。
順便說一句:雖然這不是使用不同實現的理由,但請注意,這些實現在行為上確實有所不同,而不僅僅是它們如何處理您的 Python 源代碼。 然而,這些差異通常很小,並且隨著時間的推移而消失或出現,因為這些實現正在積極開發中。 例如,IronPython 默認使用 Unicode 字符串; 然而,CPython 2.x 版本默認為 ASCII(非 ASCII 字符出現 UnicodeEncodeError 失敗),但 3.x 版本默認支持 Unicode 字符串。
即時編譯:PyPy 和未來
所以我們有一個用 C 語言編寫的 Python 實現,一個用 Java 編寫,一個用 C# 編寫。 下一個合乎邏輯的步驟:用...... Python 編寫的 Python 實現。 (受過教育的讀者會注意到這有點誤導。)
這就是事情可能會變得混亂的地方。 首先,讓我們討論一下即時 (JIT) 編譯。
JIT:為什麼和如何
回想一下,本機機器代碼比字節碼快得多。 好吧,如果我們可以編譯一些字節碼然後將其作為本機代碼運行呢? 我們必須付出一些代價來編譯字節碼(即時間),但如果最終結果更快,那就太好了! 這就是 JIT 編譯的動機,這是一種混合了解釋器和編譯器優點的混合技術。 基本上,JIT 希望利用編譯來加速解釋系統。
例如,JIT 採用的一種常見方法:
- 識別頻繁執行的字節碼。
- 將其編譯為本機機器碼。
- 緩存結果。
- 每當設置運行相同的字節碼時,取而代之的是獲取預編譯的機器碼並獲得好處(即速度提升)。
這就是 PyPy 實現的全部內容:將 JIT 引入 Python(參見附錄了解以前的工作)。 當然,還有其他目標:PyPy 的目標是跨平台、內存輕量和無堆棧支持。 但JIT確實是它的賣點。 作為一系列時間測試的平均值,據說可以將性能提高 6.27 倍。 有關細分,請參閱 PyPy 速度中心的此圖表:

PyPy 很難理解
PyPy 潛力巨大,在這一點上它與 CPython 高度兼容(因此它可以運行 Flask、Django 等)。
但是圍繞 PyPy 存在很多混淆(例如,請參閱這個創建 PyPyPy 的荒謬提議……)。 在我看來,這主要是因為 PyPy 實際上是兩件事:
用 RPython(不是 Python(我之前撒過謊))編寫的 Python 解釋器。 RPython 是具有靜態類型的 Python 子集。 在 Python 中,對類型進行嚴格的推理“幾乎是不可能的”(為什麼這麼難?考慮一下以下事實:
x = random.choice([1, "foo"])
將是有效的 Python 代碼(歸功於 Ademan)。
x
的類型是什麼? 當類型甚至沒有嚴格執行時,我們如何推斷變量的類型?)。 使用 RPython,您犧牲了一些靈活性,但反而使推理內存管理和諸如此類的事情變得更加容易,從而可以進行優化。為各種目標編譯 RPython 代碼並添加 JIT 的編譯器。 默認平台是 C,即 RPython-to-C 編譯器,但您也可以針對 JVM 和其他平台。
僅為了在此 Python 比較指南中清晰起見,我將它們稱為 PyPy (1) 和 PyPy (2)。
為什麼你需要這兩件事,為什麼在同一個屋簷下? 可以這樣想:PyPy (1) 是用 RPython 編寫的解釋器。 因此它接收用戶的 Python 代碼並將其編譯為字節碼。 但是解釋器本身(用 RPython 編寫)必須由另一個 Python 實現解釋才能運行,對吧?
好吧,我們可以只使用 CPython 來運行解釋器。 但這不會很快。
相反,我們的想法是我們使用 PyPy (2)(稱為 RPython 工具鏈)將 PyPy 的解釋器編譯為另一個平台(例如,C、JVM 或 CLI)的代碼以在我們的機器上運行,在 JIT 中添加為好吧。 這很神奇:PyPy 動態地將 JIT 添加到解釋器中,生成自己的編譯器! (同樣,這很瘋狂:我們正在編譯一個解釋器,添加另一個單獨的獨立編譯器。 )
最後,結果是一個獨立的可執行文件,它解釋 Python 源代碼並利用 JIT 優化。 這正是我們想要的! 這是一個拗口,但也許這張圖會有所幫助:
重申一下,PyPy 的真正美妙之處在於,我們可以用 RPython 為自己編寫一堆不同的 Python 解釋器,而不必擔心 JIT。 然後 PyPy 將使用 RPython 工具鏈/PyPy (2) 為我們實現 JIT 。
事實上,如果我們變得更抽象,理論上你可以為任何語言編寫一個解釋器,將其提供給 PyPy,然後獲得該語言的 JIT。 這是因為 PyPy 專注於優化實際的解釋器,而不是它所解釋的語言的細節。
作為一個簡短的題外話,我想提一下 JIT 本身絕對令人著迷。 它使用一種稱為跟踪的技術,其執行方式如下:
- 運行解釋器並解釋所有內容(不添加 JIT)。
- 對解釋的代碼進行一些簡單的分析。
- 確定您之前執行的操作。
- 將這些代碼位編譯為機器代碼。
更重要的是,這篇論文很容易獲得併且非常有趣。
總結一下:我們使用 PyPy 的 RPython-to-C(或其他目標平台)編譯器來編譯 PyPy 的 RPython 實現的解釋器。
包起來
在對 Python 實現進行了長時間的比較之後,我不得不問自己:為什麼這麼棒? 為什麼這個瘋狂的想法值得追求? 我認為 Alex Gaynor 在他的博客上說得很好:“[PyPy 是未來],因為 [它] 提供了更快的速度、更大的靈活性,並且是 Python 發展的更好平台。”
簡而言之:
- 它很快,因為它將源代碼編譯為本機代碼(使用 JIT)。
- 它很靈活,因為它只需要很少的額外工作就可以將 JIT 添加到您的解釋器中。
- 它很靈活(再次)因為你可以用 RPython 編寫你的解釋器,它比 C 更容易擴展(事實上,它很容易,有一個編寫你自己的解釋器的教程)。
附錄:您可能聽說過的其他 Python 名稱
Python 3000 (Py3k):Python 3.0 的替代命名,這是一個主要的、向後不兼容的 Python 版本,於 2008 年推出。Py3k 團隊預測,要完全採用這個新版本大約需要五年時間。 儘管大多數(警告:傳聞)Python 開發人員繼續使用 Python 2.x,但人們越來越意識到 Py3k。
- Cython:Python 的超集,包括調用 C 函數的綁定。
- 目標:允許您為 Python 代碼編寫 C 擴展。
- 還允許您將靜態類型添加到現有 Python 代碼中,使其能夠被編譯並達到類似 C 的性能。
- 這類似於 PyPy,但不一樣。 在這種情況下,您在將用戶代碼傳遞給編譯器之前強制輸入用戶代碼。 使用 PyPy,您可以編寫普通的舊 Python,編譯器會處理任何優化。
Numba:一種“即時專業化編譯器”,可將 JIT 添加到帶註釋的 Python 代碼中。 用最基本的術語來說,你給它一些提示,它會加速你的部分代碼。 Numba 是 Anaconda 發行版的一部分,這是一組用於數據分析和管理的軟件包。
IPython:與其他討論的內容非常不同。 Python的計算環境。 交互式支持 GUI 工具包和瀏覽器體驗等。
- Psyco:一個 Python 擴展模塊,也是早期的 Python JIT 工作之一。 然而,它後來被標記為“無人維護和死亡”。 事實上,Psyco 的首席開發人員 Armin Rigo 現在正在研究 PyPy。
Python 語言綁定
RubyPython:Ruby 和 Python VM 之間的橋樑。 允許您將 Python 代碼嵌入到您的 Ruby 代碼中。 您定義 Python 的啟動和停止位置,RubyPython 在 VM 之間編組數據。
PyObjc:Python 和 Objective-C 之間的語言綁定,充當它們之間的橋樑。 實際上,這意味著您可以使用 Python 代碼中的 Objective-C 庫(包括創建 OS X 應用程序所需的一切),以及 Objective-C 代碼中的 Python 模塊。 在這種情況下,用 C 編寫 CPython 很方便,C 是 Objective-C 的一個子集。
PyQt:PyObjc 為您提供 OS X GUI 組件的綁定,PyQt 為 Qt 應用程序框架提供相同的功能,讓您創建豐富的圖形界面、訪問 SQL 數據庫等。另一個旨在將 Python 的簡單性引入其他框架的工具。
JavaScript 框架
pyjs (Pyjamas):一個用 Python 創建 Web 和桌面應用程序的框架。 包括 Python 到 JavaScript 編譯器、小部件集和一些其他工具。
Brython:一個用 JavaScript 編寫的 Python VM,允許在瀏覽器中執行 Py3k 代碼。