python多重繼承的鑽石問題

來源:互聯網
上載者:User

標籤:資料   類的方法   com   例子   多重   自己   def   串連   多次   

如下,我們已經有了一個從Contact類繼承過來的Friend類

class ContactList(list):    def search(self, name):        ‘‘‘Return all contacts that contain the search value           in their name.‘‘‘        matching_contacts = []        for contact in self:            if name in contact.name:                matching_contacts.append(contact)        return matching_contactsclass Contact:    all_contacts = ContactList()      def __init__(self, name, email):        self.name = name        self.email = email        Contact.all_contacts.append(self)class Friend(Contact):    ‘‘‘通過super得到父類對象的執行個體,並且調用這個對象的__init__方法,       傳遞給它預期的參數,然後這個類做了自己的初始化,即設定phone屬性‘‘‘    def __init__(self, name, email, phone):        super().__init__(name, email)        self.phone = phone

如果要給Friend類增加一個住址的方法,住址資訊包括街道、城市、國家等。我們可以把這些字串直接傳遞給Friend中的__init__方法,另外也可以把這些字串先存放在一個元組或者字典裡面,然後再把他作為單一的參數傳遞給__init__方法。

另一種方法就是,建立一個新的Address類來專門包括這些字串,並且把這個類的一個執行個體傳給Friend類的__init__方法。這樣做的好處是在其他的如建築、商業、組織中重用這個Address類。

class AddressHolder:    def __init__(self, street, city, state, code):        self.street = street        self.city = city        self.state = state        self.code = code

現在問題來了,在已經存在的從Contact類繼承過來的Friend類中如何增加一個住址。

最好的方法是多重繼承,但是這樣會有兩個父類的__init__方法需要被初始化,並且他們要通過不同的參數進行初始化,如何來做呢?讓我們從一個天真的方法開始,對上述代碼的Friend進行改寫:

class Friend(Contact, AddressHolder):    def __init__(self, name, email, phone, street, city, state, code):        Contact.__init__(self, name, email)        AddressHolder.__init__(self, street, city, state, code)        self.phone = phone        

上述從技術層面上是可以工作的,但是存在一些問題。

首先,如果我們忽略顯式地調用初始化函數可能會導致一個超類未被初始化。在這裡並不明顯,但是在另一些情境會導致程式崩潰,比如把資料插入到一個未串連的資料庫裡。

第二,由於這些類的層次結果,可能會導致某個超類被調用多次。如所示。

       

從中,Friend中的__init__首先調用了Contact中的__init__,隱私初始化了object(所有類都繼承於object)。Friend然後又調用AddressHolder的__init__,又一次隱式初始化了object超類,父類被建立了兩次。在我們的這個情況下,它是無害的,但是在一些情境中,會帶來災難。(每一個方法的調用順序可以通過__mro__修改,這裡略)

 

再看如下的一個例子,我們有一個基類,該基類有一個call_me方法,有兩個子類重寫了這個方法,然後第3個類通過多重繼承擴充了兩個方法。這稱為鑽石繼承。

       

從技術上來說,在python3的所有多重繼承都是鑽石繼承,因為所有的類都從object繼承,中的object.__init__同樣是一個鑽石問題。把轉化成代碼如下:

class BaseClass:    num_base_calls = 0    def call_me(self):        print("Calling method on Base Class")        self.num_base_calls += 1class LeftSubclass(BaseClass):    num_left_calls = 0    def call_me(self):        BaseClass.call_me(self)        print("Calling method on Lef Subclass")        self.num_left_calls += 1class RightSubclass(BaseClass):    num_right_calls = 0    def call_me(self):        BaseClass.call_me(self)        print("Calling method on Right Subclass")        self.num_right_calls += 1class Subclass(LeftSubclass, RightSubclass):    num_sub_calls = 0    def call_me(self):        LeftSubclass.call_me(self)        RightSubclass.call_me(self)        print("Calling method on Subcalss")        self.num_sub_calls += 1

調用並得到如下輸出:

$ python -i zuanshi.py >>> s = Subclass()>>> s.call_me()Calling method on Base ClassCalling method on Lef SubclassCalling method on Base ClassCalling method on Right SubclassCalling method on Subcalss>>> print(s.num_sub_calls, s.num_left_calls, ... s.num_right_calls, s.num_base_calls)1 1 1 2

基類的call_me被調用了兩次。但這不是我們想要的,如果在做實際的工作,這將導致非常嚴重的bug,如銀行存款存了兩次。

對於多重繼承,我們只想調用“下一個”方法,而不是父類的方法。實際上,下一個方法可能不屬於當前類或者當前類的父類或者祖先類。關鍵字super可以解決這個問題,如下是代碼重寫:

class BaseClass:    num_base_calls = 0    def call_me(self):        print("Calling method on Base Class")        self.num_base_calls += 1class LeftSubclass(BaseClass):    num_left_calls = 0    def call_me(self):        super().call_me()        print("Calling method on Lef Subclass")        self.num_left_calls += 1class RightSubclass(BaseClass):    num_right_calls = 0    def call_me(self):        super().call_me()        print("Calling method on Right Subclass")        self.num_right_calls += 1class Subclass(LeftSubclass, RightSubclass):    num_sub_calls = 0    def call_me(self):        super().call_me()        print("Calling method on Subcalss")        self.num_sub_calls += 1

執行結果如下:

>>> s = Subclass()>>> s.call_me()Calling method on Base ClassCalling method on Right SubclassCalling method on Lef SubclassCalling method on Subcalss>>> print(s.num_sub_calls, s.num_left_calls, ... s.num_right_calls, s.num_base_calls)1 1 1 1

首先,Subclass的call_me方法調用了super().call_me(),其實就是引用了LeftSubclass.call_me()方法。然後LeftSubclass.call_me()調用了super().call_me(),但是這時super()引用了RightSubclass.call_me()。需要特別注意:super調用並不是調用LeftSubclass的超類(就是BaseClass)的方法。它是調用RightSubclass,雖然它不是LeftSubclass的父類!這就是下一個方法,而不是父類方法。RightSubclass然後調用BaseClass,並且通過super調用保證在類的階層中每一個方法都被執行一次。

 

參考:

1、《Python3 物件導向編程》 [加]Dusty Philips 著

 

python多重繼承的鑽石問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.