Python3.4 Tutorial9 - Classes (Part 3)

walkerMK發表於2015-09-23

9.5 繼承(Inheritance)

    當然,一個有“類”的語言中如果不支援繼承的特性,是沒有什麼價值的。派生類(Derived Class)的定義語法如下:

class DerivedClassName(BaseClassName):
     <statement-1>
     .
     .
     .
     <statement-N>

    BaseClassName這個名字必須定義在包含派生類定義的作用域中。在放基類名的地方,任何其他的表示式也是可以使用的。例如,當基類定義在其他模組的時候這會很有用:

class DerivedClassName(modname.BaseClassName):

    派生類的執行和基類一樣。當類物件構造完成後,基類物件也會被記住。在處理屬性引用時會用到:如果需要的屬性在當前類中沒找到,會繼續在基類中搜尋。如果基類本身又是另一個類的派生類的話,這項規則會遞迴的執行。

    派生類的例項化沒有什麼特別之處:DerivedClassName()建立類的一個新例項。方法引用會有如下的處理:相應的類屬性會被查詢,如果有必要的話會在基類鏈上繼續查詢,只有過程中找到一個函式物件這個方法引用就是合法的。

    派生類可以重寫基類的方法。由於方法在呼叫同一個物件的其他方法時沒有特殊許可權,父類的一個方法呼叫這個父類定義的另一個方法時,會呼叫到派生類中重寫的方法。(對於C++程式設計師來說:Python中所有的方法都是virtual的。)

    在派生類中重寫一個方法實際上是想擴充基類的同名方法,而不只是替換它。有一個直接呼叫基類方法的簡單方式:直接呼叫BaseClassName.methodname(self, arguments)。有時候這對客戶也是有用的。(注意,這段程式碼只有當BaseClassName在全域性作用域中可訪問時才是可用的。)

    Python有兩個和繼承相關的內建函式:

  • 使用isinstance()檢查一個例項的型別:isinstance(obj, int)只有當obj.__class__是int或其他派生自int的類時才會是True
  • 使用issubclass()檢查類的繼承關係:issubclass(bool, int)True,因為bool是int的一個子類。然而,issubclass(float, int)False,因為float不是int的子類。

9.5.1 多繼承(Multiple Inheritance)

    Python是支援多繼承的。一個類帶有多個基類的定義方式如下:

class DerivedClassName(Base1, Base2, Base3):
     <statement-1>
     .
     .
     .
     <statement-N>

    在大多數情況下,你可以簡單的認為在父類中查詢繼承的屬性是一個深度優先、從左到右的過程,同一個類裡在繼承結構中重疊的部分不會被搜尋兩次。因此,如果一個屬性在DerivedClassName中找不到,它會在Base1中查詢,然後(遞迴的)在Base1的基類中查詢,如果都沒有找到,會去Base2中查詢,以此類推。

    實際的查詢過程會稍微複雜一些;方法的搜尋順序會根據super()的呼叫而動態變化(附上原文:method resolution order changes dynamically to support cooperative calls to super())。這種方法在其他的多繼承語言中以call-next-method被熟知,並且比單繼承語言中的超類呼叫更有用。

    動態排序是很有必要的,因為所有的多繼承關係都是呈一個或多個菱形結構(在從最底層的類到父類的多條路徑中至少有一條是可以訪問到的)。例如,所有的類都繼承自object,所以任何一個多繼承都提供了不止一條到達object的路徑。為了保證基類可以被多次訪問到,動態演算法將查詢順序線性化,使每個類中都是保持從左到右的順序(翻譯的比較混亂,原文是:the dynamic algorithm linearizes the search order in a way that preserves the left-to-right ordering specified in each class),這樣每個父類只被呼叫一次,就不再改變了(這意味著,一個類被繼承時不會影響它父類的搜尋順序。)。綜上考慮,這個屬性使得使用多繼承設計一個可靠的、可擴充的類成為可能。更多的細節,請參考https://www.python.org/download/releases/2.3/mro/

9.6 私有變數(Private Variables)

    在Python中,不存在無法訪問的“私有”例項變數。然而,在大多數的Python程式碼中都會遵循這樣的約定:以下劃線'_'為字首的名稱(例如_spam)應當作為非公有的API來對待(無論它是函式、方法還是一個資料成員)。應該把它作為一個實現細節對待,並且如果改變了也不會有任何通知。

    因為有一箇中合法的使用類私有成員的情況(即為了避免和子類中的名稱衝突),所以為這個機制提供了有限的支援,稱為名稱重整(name mangling)。任何使用__spam形式(前面最少有兩個下劃線,後面最多跟一個下劃線)的識別符號都會在結構上被替換為_classname__spam,其中classname是當前類名去掉開頭下劃線的部分。識別符號在語法上的位置不會影響重整的完成,只要它出現在類的定義中即可。

    名稱重整用於,讓子類在不打斷內部方法呼叫的情況下重寫父類方法。例如:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

    請銘記,重整的規則的設計是用來避免衝突的;所以,仍然可以訪問或修改一個被認為是私有的變數。在特殊情況下這也可能是有用的,例如除錯的時候。

    注意,傳遞給exec()eval()的程式碼不會認為呼叫類的類名是當前類;這和global語句的作用類似,位元組編譯在一起的程式碼也有這個限制。相同的限制也適用於getattr()setattr()delattr(),以及直接引用__dict__的時候。

9.7 雜記(Odds and Ends)

    有時候,有和Pascal中的“記錄(record)”或C中的“結構體(struct)”相似的資料型別會很有用,可以將一些命名後的資料繫結在一起。一個空的類定義可以很好的做到這個:

class Employee:
    pass

john = Employee() # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

    一段Python程式碼中如果需要一種獨特的抽象資料型別,通常可以將一個類傳遞過去,而不是使用模擬那種資料型別的方法來實現。例如,有一個函式用來格式化檔案物件中的一些資料,你可以定義一個帶read()和readline()方法的,從字串緩衝區中讀取資料類來代替檔案物件,並將它作為引數傳遞。

    例項方法物件也有屬性:m.__self__代表擁有方法m()的那個例項物件,m.__func__是代表方法的那個函式物件。

9.8 異常也是類

    使用者定義的異常也是被類標識的。應用這個機制,可以建立出可擴充的異常繼承體系。

    raise語句有兩個新的(語義上)合法的形式:

raise Class

raise Instance

    第一種形式,Class必須是type的例項或從它的一個派生類。第一種形式是下面的簡寫:

raise Class()

    在except子句中的類和被丟擲的異常類自身或它的基類是相容的(但是反過來卻不行——except子句中列出的派生類,和它們基類的異常是不相容的)。例如,下面的程式碼會按順序輸出B,C,D:

class B(Exception):
    pass
class C(B):
    pass
class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

    注意,如果這些異常子句的順序倒過來(except B放在第一個位置),它會列印B,B,B——第一個except子句總會匹配上。

    當一個未處理的異常的錯誤訊息被列印出來時,異常類的類名會被列印,後面跟著一個冒號和一個空格,最後是使用內建函式str()轉換為string的異常例項。

相關文章