為什麼有這麼多 Python?

oschina發表於2013-09-28

  Python是出類拔萃的

  然而,這是一句非常模稜兩可的話。這裡的"Python"到底指的是什麼? 是Python的抽象介面嗎?是Python的通用實現CPython嗎(不要把CPython跟Cython搞混了)?亦或者指的完全是其他的東西呢?可能我另外指的是Jython,或者IronPython,或者是PyPy。也或者轉而談論的又是RPython或者RubyPython(這兩者是完全不同的東西)。

  上面提到的那些技術經常被提起和引用, 它們的使用目的和場景是完全不一樣的(至少,它們的操作方式是完全不一樣的)

  自從我使用Python工作以來,我已經用過了各種各樣的.*ython工具了。但是直到最近我才花時間去理解到底它們是幹嘛的,它們是怎樣工作的,為什麼它們是不可或缺的。

  在這篇文章裡面,我會介紹各種Python的實現,最後以對PyPy的介紹結尾, 因為我個人認為它是Python的未來。

  所有的都從理解什麼是"Python"開始。

  如果你對機器碼,虛擬機器之類的很熟了,你可以跳過開頭,直接從 "即時編譯: PyPy和它的未來" 這部分開始看起。

  Python是解釋型的還是編譯型的?

  這是個Python新人都會迷惑的問題。

  首先需要明瞭的是Python只是一個介面。有一個關於Python應該做什麼以及怎麼做的具體說明(就像其他任何介面一樣 ),並且對應的有很多具體的實現(也像其他介面一樣)。

  其次需要知道的是“解釋型”和“編譯型”是具體實現的特性,而不是介面的特性。

  所以,這個問題本身就沒有組織好。

  Python是解釋型還是編譯型的?這個問題真的沒有組織好。

  對使用最廣泛的實現(CPython:用C實現的,通常簡單的說成Python,若你不知道我所說的這些,那很肯能你在使用的就是CPython)而言,這個問題的答案是:解釋型,但帶有一些編譯型特徵。CPython把Python原始碼編譯*成位元組碼,之後再解釋這些位元組碼,執行之。

  *注意:這個編譯不是通常意義上的編譯。通常我們說的編譯,是指把高階語言程式碼轉換成機器碼。但這裡實際上是一種種類的編譯。(譯者,這句話不是很懂,原文是it is a ‘compilation’ of sorts,不知作何解,求教各位讀者。)

  再詳細看下上面的答案吧,這有助於我們理解本文中後面會講到的幾個概念。

  位元組碼 vs. 機器碼

  瞭解位元組碼和機器碼(或者native code)的區別是很重要的,最好的辦法或許是看看例子:

  • C程式碼被編譯成機器碼,將在處理器上直接執行。每一條指令控制CPU工作。
  • Java程式碼被編譯成位元組碼,將在Java虛擬機器(JVM)這個抽象的計算機上執行。每一條指令由JVM處理,JVM同計算機本身之間互動。

  簡而言之:機器碼快的多,但位元組碼更易遷移,也更安全。

  機器碼隨機器的變化而變化,但位元組碼在所有的機器上都是一樣的。有人可能會認為機器碼是對特定環境優化了的。

  1. 回到CPython,工具鏈的執行過程如下:
  2. CPython編譯你的Python原始碼,生成位元組碼。

  位元組碼隨後在CPython虛擬機器上執行。

  初學者常常因為看到.pyc檔案而假設Python是編譯型的。這也有一些合理性:.pyc檔案正式之後要解釋的位元組碼檔案。所以,你若之前執行過你的Python程式碼,生成了.pyc檔案,再次執行時就要快得多,因為不需要再次編譯生成位元組碼了。

  可選的虛擬機器:Jython,IronPython等

  正如我之前所述,Python有很多實現。前面也提到,CPython是最通用的。這是一個用C實現的,被認為是”預設“的實現。

  但其他的呢?其中最顯赫的之一就是Jython,一個用Java實現的採用了JVM的實現。CPython生成在CPython虛擬機器上執行的位元組碼,而Jython生成在JVM上執行的java位元組碼(這同編譯Java程式生成java位元組碼的過程是一樣的)。

  ”為啥你要用其他的實現?”,你可能會如此發問。好吧,對開發者而言,不同的實現對不同的技術難題的支援程度不一樣。

  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虛擬機器的平臺上,這是微軟的 Common Language Runtime (CLR),同JVM相對應。

  你可能會說,Jython:Java::IronPython:C#。它們各自執行在相同的虛擬機器上,你能從你的IronPython中匯入C#的類,從你寫的Jython程式碼中帶入Java類,deng

  你完全可以不用任何非CPython的實現就能完成你手上的任何工作。但是使用這些技術也是有很多的好處的,大部分取決於你現在所使用的技術棧。 你使用了很多基於JVM的語言?Jython就是為你準備的。使用的都是.NET世界的語言?那麼你應該試試IronPython了(或許你已經在用了)

  順便說一下(儘管這不是使用不同的實現的理由),注意Python的各種實現在對待你的Python原始碼的時候所做的處理方式是完全不一樣的。然後這些差異是很小的,由於這些實現都在不停的發展改進中,隨著時間的推移,這些差異會慢慢融合和相容。比如,IronPython預設情況下使用Unicode字串,但是在2.x版本的CPython中預設是ASCII字串(如果使用了非ASCII字串,會丟擲一個UnicodeEncodeError錯誤),但是在3.x版本里面CPythong已經預設支援Unicode字串了。

  即時編譯: PyPy和它的未來

  我們已經有了一個使用C寫的Python實現,一個用Java寫的,一個用C#寫的。接下來就是:用Python寫的Python實現(有心人可能會注意這句話有點問題,是個死迴圈,^_^)

  接下來我們看下什麼地方容易搞混淆。首先,我們討論下即時編譯器JIT

  JIT: 為什麼會有這個?它的原理是什麼?

  大家都知道本地機器碼的速度比位元組碼的速度快很多。那麼,如果我們能將一些位元組碼直接編譯成本地機器碼再去執行它會怎樣呢?我們必須花費一些代價(比如時間)在編譯位元組碼到本地機器碼上,如果最終的執行時間更快,那麼這個代價就是值得的。這就是JIT編譯器的動機,一種混合瞭直譯器和編譯器好處的技術。簡單來講,JIT就是想通過編譯技術提升指令碼直譯器系統的速度。

  例如, 被JIT(及時編譯)採用的通用方法:

  1. 標識被經常執行的位元組碼。
  2. 把其編譯成本地的機器碼。
  3. 快取該結果。
  4. 當同樣的的位元組碼再次被執行的時候,會取預編譯的機器碼,得到好處(例如速度提升)。

  這是關於PyPy的用處: 把JIT代入Python語言 (參看前面成果的附錄).當然也有其他目的: PyPy 目標是成為一個跨平臺,輕記憶體,支援stackless(譯註:stackless為python提供微執行緒擴充套件,具有併發特性)。 但是及時編譯才是它真正的賣點。 基於一系列時間測試的平均, 據說效能上能提高6.27倍. 停一下, 看看下面這個由PyPy Speed Center提供的圖表:

  PyPy is Hard to Understand

  PyPy具有巨大的潛力,在這一點上,它與CPython高度相容所以它能執行Flask,Django等等)。

  但關於PyPy有許多困惑 (例如,荒謬的建議創造一種PyPyPy…語言). 按我的觀點,那主要是因為PyPy實際上是兩種東西:

  一種用RPython (非Python (我之前撒謊了))編寫的Python直譯器。 RPython是Python的子集,具有靜態型別。在Python裡,最難嚴格推論型別 (為什麼這麼困難,考慮下下面的事實:

x = random.choice([1, "foo"])
  1. 將是合法的Python程式碼 (歸功於 Ademan). x的型別是什麼? 我們怎麼推出變數的型別,當型別還沒有被嚴格實施?)通過RPython,你犧牲了一些靈活性, 但使得記憶體管理和優化大大的容易。

  2. 一個編譯RPython程式碼為了各種目標和加入及時編譯的編譯器。 預設平臺是C,也就是從RPython到C編譯器,但你也可以瞄準JVM或者其他。

  只為清晰,我將引用這些PyPy(1)和PyPy(2)。

  為什麼你在同一層面下同時需要這兩者? 你可以這樣想一下:PyPy(1)是一個用RPython寫的直譯器,因此它能載入使用者的Python程式碼並將它編譯成位元組碼。但是這個用RPython寫的直譯器本身要能執行,就必須要被另外一個Python實現去解釋,對不?

  我們可以直接用CPython去執行這個直譯器。但是這個還不夠快

  取而代之,我們使用了PyPy(2)(參考 RPython的工具鏈)去編譯這個PyPy的直譯器,生成其他平臺(比如C, JVM或CLI)程式碼在我們的機器上執行,並且還加入了JIT特性。這個很神奇:PyPy動態的將JIT加入一個直譯器,生成它自己編譯器!(這就是核心原理:我們在編譯一個直譯器,並同時加入了另外一個單獨的編譯器到裡面去)。

  最終結果就是一個融合了JIT優化特性的單獨的可執行檔案,用來解釋執行我們的Python原始碼。這就是我們之前想要達到的效果。這麼講可能比較拗口,下面這張圖可能會解釋的比較清楚點:

  再次重申下,PyPy真正可貴之處在於我們可以利用RPython實現各種不同的Python直譯器,不用去關心JIT(除了一些小的提示外)。PyPy到時候會利用RPython工具鏈/PyPy(2)為我們自動實現JIT

  事實上,我們還可以更抽象一點,我們理論上可以寫一個適用於任何語言的直譯器,然後將它扔給PyPy,最後獲得那種語言的JIT。原因是PyPy僅僅關心的是優化直譯器,而不會去關心這個直譯器到底解釋的是什麼語言。

  理論上你自己可以寫一個適用於任何語言的直譯器,然後將這個直譯器傳給PyPy,最後你得到這個語言的一個JIT。一個簡單的題外話,我這裡想提一下,JIT本事是相當棒的。它使用了一種叫做跟蹤的技術,按照下面的步驟執行:

  1. 執行直譯器並解釋執行所有程式碼(還沒有加入JIT特性)
  2. 對被解釋過的程式碼做一些記錄
  3. 確認你已經執行過的操作
  4. 將確認過的這些程式碼編譯成本地機器碼

  想獲取更多資訊,可以參考這篇文章,易於理解,並且非常有趣

  最後收尾:我們使用PyPy的RPython-to-C(或者其他目標平臺)編譯器去編譯PyPy的基於RPython實現的直譯器。

 結尾

  為什麼它如此的偉大?為什麼這個瘋狂的想法值得我們去追求?我想Alex Gaynor已經在他的部落格上面做了很好的解釋了:“[PyPy就是未來] 因為[它]提供了更快的速度,更大的靈活性,並且對於Python的成長也提供了一個更好的平臺”

  總之:

  • 它很快,因為它將原始碼編譯成了本地機器碼(使用了JIT)
  • 它很靈活,因為除了極少數的額外工作需要做外,它就能將JIT加入你的直譯器中
  • 它還是很靈活,因為你能使用RPython實現你的直譯器,這個比其他的(比如C語言)更易擴充套件。事實上,它是如此的簡單,這裡有一篇教程教你如何實現你自己的直譯器

 附錄: 其他一些你可能已經聽過的名字

  • Python 3000 (Py3k): Python 3.0的一個別名,2008年釋出的一個主要版本,但是它並不向後相容.。Py3k團隊預測這個版本被完全採用可能需要5年時間.。現在絕大多數(注意:這個是江湖傳聞)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工作。

  語言繫結

  • RubyPython: Ruby和Python虛擬機器的一座橋樑。允許你在你的Ruby程式碼中嵌入Python程式碼。你定義Python的起始位置,然後RubyPython負責在不同VM直接傳遞整理資料。

  • PyObjc: Python和Objective-C語言直接的橋樑。實際上,這意味著你能在你的Python程式碼中使用Objective-C的庫(包括建立一個OS X應用程式所需要的一切),反過來在Objective-C裡面也可以使用Python的模組。這樣的話,CPython用C語言來實現就很方便了,因為C語言是Objective-C的一個子集。

  • PyQt: 同PyObjc幫你繫結OS X GUI元件類似,PyQt幫你繫結Qt應用程式框架,讓你可以建立豐富的圖形介面,訪問關聯式資料庫等等。另外的一個旨在幫你簡化從Python到另外的框架的工具。 

  JavaScript 框架

  • pyjs (Pyjamas): Python中一個建立web和桌面應用程式的框架。包含一個Python-to-JavaScript的編譯器和其他一些工具。

  • Brython: 一個使用JavaScript語言寫的Python虛擬機器,可以讓Py2k 程式碼在瀏覽器中執行。

  原文地址:http://www.toptal.com/python/why-are-there-so-many-pythons

相關文章