9. Python的類是如何運作的?

本系列文章譯自Python之父 Guido van Rossum 的系列部落格“The History of Python”。這個部落格系列對我們理解Python及其演變很有幫助,經Guido同意,在這裡翻譯推薦給大家,希望大家喜歡,也請大家多多指教!
本系列譯文目錄: http://blog.kantli.com/theme/1
1 基於 C語言的底層機制
說來你可能不信,Python 開發一年後,我們才加入“類(class)”的概念。為了解這段歷史,首先要理解 Python 的一些底層實現。
Python 的底層,實際上是基於一個用C語言實現的程式碼解析器,或者說“虛擬機器”,以及也是用C語言實現的幾種基本資料型別。雖然 Python 底層架構使用了各種“物件”,但因為C語言不是面嚮物件語言,所以這些物件的實現,實際上是基於一些特定的資料結構與方法指標。
Python 虛擬機器定義了每個物件型別都可以,或者必須實現的數十種標準方法(比如“get attribute”、“add” 、“call”等)。每個物件型別都有一個靜態儲存的資料空間,其中包含一系列方法指標,每個指標指向一種標準方法。不過,有些標準方法不是必須實現的,於是會出現空指標,這時,Python 虛擬機器會丟擲執行時錯誤,或者有時也提供這些方法的預設實現。
另外,物件型別的資料空間裡也包含很多其它資料,包括一個儲存這個型別的特有方法的列表。每種方法由一個字串(方法名稱)和一個方法指標(具體實現)組成。
Python 獨特的自省(introspection)機制,就是基於其獨特的,可以在執行時作為一種物件的型別資料結構。
Python 的底層完全是基於 C 語言的,事實上其所有標準方法都是用 C 語言寫的。最開始時,Python 解析器只支援純粹的 Python 函式和用 C 語言實現的這些方法。我記得應該是我們當時的同事,Siebren van der Zee,建議我們應該像 C++ 一樣支援“類”,從而實現自定義物件。
2 類的實現
我採用了最簡單的設計來實現使用者自定義物件:所有物件的例項都用一種新的內建物件表示,這個內建物件包含一個指標和一個字典,指標指向這個例項對應的“類物件(class object)”,而字典中包含了這個物件例項的各種變數,也被稱為“例項字典”。
在此實現中,例項字典中的變數是這個例項所具有的獨特變數,而類物件則包含這個類的所有例項所共享的東西——尤其是方法。
而在實現類物件的時候,我依然採用了最簡單的設計:類的所有方法都被儲存在一個字典中,其對應的鍵值就是方法的名稱。我把它叫做“類字典(class dictionary)”。
為支援繼承,類物件也會儲存指向父類物件的指標。當時,我對類的理解還非常淺薄,不過對 C++ 剛支援的多重繼承特性有所瞭解。因此我想,既然要支援繼承,不如也同時支援一個簡單版的多重繼承。於是,每個類物件都允許有多個父類。
基於這種實現,物件的底層運作機制其實非常簡單:不論是改變例項變數還是類變數,都只是修改對應字典的資料而已。
比如說,設定例項變數時,只需要更新它的例項字典;類似地,獲取例項變數時,首先檢查例項字典中是否有這個變數,如果沒有,就在類物件以及父類物件的字典中查詢。
不過,在父類物件中查詢變數還涉及一些其它問題。例項從類物件和父類物件中獲取變數的過程,一般與查詢方法的過程一樣。如前文所說,方法儲存在類物件字典中,由這個類的所有例項共享。因此,呼叫方法的時候,一般不會在例項字典中找到,而必須查詢類物件的字典,之後再從父類物件字典中遞迴查詢。
Python 的最初的預設方法解析順序(method resolution order,MRO)是採用深度優先,從左至右演算法。不過,後來的版本採用了更成熟的 MRO (C3演算法),我們會在之後的博文中討論。
3 簡單,但靈活
我希望 Python 類的實現儘量簡單。因此,Python 在查詢方法的時候,並不會做錯誤檢查或一致性驗證。比如說,如果一個類過載了父類的一個方法,Python 不會檢查這個過載的方法是否與父類方法有相同的引數,或者相同的呼叫方式。前文所說的方法解析演算法,只是單純地返回它找到的第一個方法而已。
另外,這種設計還導致了一些其它特性。比如說,雖然類字典最初是用於儲存方法的,但也不是不能儲存其它物件。因此,如果在類字典中儲存整數或字串,就可以作為類變數——即由所有例項共享的變數。
Python 類的實現非常簡單,但也非常靈活。
比如說,因為採用了這種實現方式,Python 中的類也是“一級物件(first-class objects)”,因而在程式執行時可以很容易內省到(introspected),而我們也可以對類進行動態修改。
舉例來說,在類物件建立後,通過更新類字典,我們依然可以增加或修改類的方法。(作者注:後來的新式類中,dict 類的修改被控制了,我們依然可以動態地修改類,但必須呼叫引數設定方法,而不是直接使用 dict 類。)而又因為 Python 是動態型別語言,這就意味著,這個類,及其子類的所有例項,都會立即更新其方法。
類似的,通過修改例項字典,例項的變數也可以動態增加、修改或刪除(SmallTalk 語言在物件建立時就限制了例項的變數,與之相比,Python 的這個特性可以說相當自由了)。
公眾號:ReadingPython
