Python直譯器簡介(4):動態語言

colleen__chen發表於2014-01-24

這是Python直譯器簡介的第四部分。閱讀第一部分第二部分第三部分。如果你喜歡這個系列的話,那就把它分享到Hacker School吧。我是那裡的管理員。

當我開始研究python內部工作的時候,我一直很困惑為什麼能夠進行“編譯”的python還是“動態語言”。通常,我們都將這兩個詞作為一對反義詞——“動態語言”1包括Python、Ruby和 Javascript等,而“編譯語言”則包括C、Java和Haskell等。

人們通常所說的“編譯語言”是指能夠編譯出適用於x86、ARM等的指令2(作用於真正的機器)的語言。一種“解釋性”語言不是根本就沒有編譯器3就是隻編譯成一箇中間表示,比如位元組碼。位元組碼的指令不是作用於任何硬體的,而是虛擬機器。Python就屬於後者:Python的編譯器將生成的位元組碼傳遞給Python直譯器。4

Python直譯器將通過虛擬機器做許多工作使得位元組碼得以解釋。至於虛擬機器,我們會在第五部分討論。

目前為止,我們對編譯和解釋的概念還是抽象的。通過下面這個例子我們能更加清晰:

這是一個函式以及它的位元組碼通過反彙編程式的結果。一旦我們定義了modulus函式,它就被編譯了並且生成了一個不能被修改的程式碼物件。

這應該很容易推算。鍵入modulus(%)使編譯器發出指令BINARY_MODULO。所以如果我們要計算一個餘數的話,這個函式就能夠發揮作用了。

這樣看,它正常工作。但如果我們不傳遞數字給它呢?

慢著,這是怎麼了?你以前也許見到過,但它通常是這麼寫的:

當BINARY_MODULO處理兩個字串的時候,它預設執行字串插值而不是求餘數。這就是動態型別的典型例子。編譯器在生成modulus的程式碼物件的時候,它完全不知道x和 y是字串、數字還是其它型別。它只是發出一些指令而已:載入一個名字,載入另一個名字,BINARY_MODULO這個兩個物件,然後返回結果。至於弄清BINARY_MODULO真正指什麼則是直譯器的工作。

我想我們忽略了一些東西。我們的函式modulus能夠計算餘數或者格式化字串,還有嗎?如果我們定義一個能夠響應__mod__的自定義物件的話,我們還能做很多。

雖然還是那個有著同樣位元組碼的函式modulus,但是當它傳遞不同種物件的時候,作用並不相同。Modulus也可能報錯——比如,如果我們使用一個不能執行__mod__的物件就會有TypeError。更不可思議的是,當__mod__被呼叫的時候,我們能寫出一個造成SystemExit的自定義物件。__mod__函式能夠對檔案進行寫入操作;改變一個全域性變數或者刪除物件的另外一個性質。我們幾乎能做任何事。

這也是難以優化Python的原因之一:在編譯程式碼物件和生成位元組碼的時候,你並不知道會有怎樣的結果。編譯器根本不關心結果如何。就像Russell Power 和 Alex Rubinsteyn 所說的:“我們能使解釋性語言Python有多快?”,“由於不用宣告型別資訊,幾乎每個指令都要像INVOKE_ARBITRARY_METHOD一樣來執行。”

儘管“編譯”和“解釋”的定義在通常情況下是很難區分的,但是對於Python來說卻很簡單。編譯工作就是生成程式碼物件,包括位元組碼。而翻譯的工作就是翻譯位元組碼,執行指令。Python保持“動態”特性的原因之一就是同樣的位元組碼能夠有不同的作用。更普遍的說法就是Python直譯器的工作比編譯器的多一些。

在第五部分,我們就會討論虛擬機器和直譯器了。

注:

  1. 你通常聽到的“解釋語言”就是“動態語言”。
  2. 感謝David Nolen給出了這個定義。“語法分析”、“編譯”和“解釋”的定義很易混淆。
  3. 一些語言並不在R、Scheme、二進位制檔案中都進行編譯。這與你使用的開發工具和你對“編譯”的定義有關。
  4. 儘管我和以往一樣是基於CPython 和Python 2.7來寫本文的,但是本文大部分內容也適於其它開發工具。

相關文章