iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0

「老豆」,粵俚,老爸也。


  • 今天談的就是John Doe(張三)一個人有幾個老爸的問題。在物件導向程式設計世界,這叫「多重繼承」(Multiple Inheritance)。
  • 「多重繼承」就是某個子類別繼承自兩個以上的父類別,好比本篇標的John Doe有好幾個父親。
    • 不是所有物件導向的程式語言都支援多重繼承。像C-like這掛語言,就只有最早誕生的C++允許多重繼承。後生晚輩的Java和C#反而不支援,而改以「介面」(Interface)取代多重繼承機制。
  • 延續上篇的例子,Hardwood(闊葉樹)和Conifer(針葉樹)均繼承自Tree。後來發現另外有一種樹Timba既屬於Hardwood,也屬於Conifer,但又有它自己的特性,所以乾脆就同時繼承HardwoodConifer
  • Tree, Hardwood, ConiferTimba四個類別之間的關係,圖示如下:
    https://ithelp.ithome.com.tw/upload/images/20221009/20148485u4FQ6Bd6zl.png
  • 先看一下C++的多重繼承:
    #include <iostream>
    using namespace std;
    
    class Tree{
        public:
            string breed;
            Tree(string myBreed){
                breed = myBreed;
                cout << "Tree's constructor.  I am a " << this->breed << '.' << endl;
            }
        };
    
    class Hardwood : public Tree{    // 繼承自Tree。
        public:
            Hardwood(string breed): Tree(breed){
                cout << "Hardwood's constructor." << endl << endl;
            }
        };
    
    class Conifer : public Tree{      // 繼承自Tree。
        public:
            Conifer(string breed): Tree(breed){
                cout << "Conifer's constructor." << endl << endl;
            }
    
        };
    
    class Timba : public Hardwood, public Conifer{    // 同時繼承自Hardwood及Conifer。
    public:
        Timba(string breed): Hardwood(breed), Conifer(breed){
            cout << "Timba's constructor." << endl;
        }
    };
    
    
    int main(void){
        Timba timba("timba");
        return 0;
    }
    
  • 輸出如下:
    https://ithelp.ithome.com.tw/upload/images/20221007/20148485D6bz9Iv1gc.png
  • 我們的重點是Python,C++的多重繼承何以如此輸出,就不說明了。事實上筆者對C++的多重繼承了解有限,要解釋也非常吃力,沒有必要浪費精神在此上面。

Python的多重繼承

  • Python是支援多重繼承的。以下沿用之前的Tree, Hardwood, ConiferTimba四個類別關係,改用Python撰寫。為了聚焦於繼承,所有屬性都設成公開,省去寫property的「麻煩」:

    class Tree():
        def __init__(self, breed: str, age: int):   # constructor
            self.breed = breed
            self.age = age
    
        def show_info(self):
            print(f'{self.breed=:10}{self.age=:<10,}')
    
    class Hardwood(Tree):
      def __init__(self, breed: str, age: int, defoliation: dict, puberty: dict):   # defoliation是落葉,puberty是開花期。
          super().__init__(breed, age)     # 呼叫父類別的constructor。
          self.defoliation = defoliation   # 子類別自己的屬性。
          self.puberty = puberty           # 子類別自己的屬性。
    
    class Conifer(Tree):
      def __init__(self, breed: str, age: int):
          super().__init__(breed, age)    # 呼叫父類別的constructor。
        #   self.price = price            # 子類別自己的屬性。
    
      def sell(self, seller: str, buyer) -> bool:   # 子類別自己的方法。
        ...   # 實作略
        print(f'{seller=:20}{buyer=:20}')
        return True
    
    class Timba(Hardwood, Conifer):
        def __init__(self, breed: str, age: int, defoliation: dict, puberty: dict, durability: int, strength: int):
            super().__init__(breed, age, defoliation, puberty)  # 呼叫父類別的constructor。
            self.durability = durability
            self.strength = strength
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221007/20148485pg58uXCMYL.png

  • 噢,我好像不小心走錯路了。一來就用了一個過於複習的例子,這絕對不是好的展示方式。

  • 就讓我們再走一遍,從最簡單的出發吧。為了簡化,以下的各個情境全省去屬性,只用方法來說明Python的多重繼承機制。

  • 情境-1:

    class Tree():
        def __init__(self, breed: str):
            self.breed = breed
        def show_breed(self):
            print(f'Tree: {self.breed = :12}')
    
    class Hardwood(Tree):      # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Hardwood: {self.breed = :12}')
    
    class Conifer(Tree):       # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Conifer: {self.breed = :12}')
    
    class Timba(Hardwood, Conifer):  # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
        ...         # 完全不實作,看效果如何。
    
    
    tree = Timba('timba')
    tree.show_breed()
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221007/20148485BwH0DlVyza.png

  • 此情境Timba類別沒有自己的實作,所以在呼叫show_breed()時會往上找其父類別。但它有兩個父親,而且兩個老爸都有實作show_breed(),而Timba類別是這樣定義的:class Timba(Hardwood, Conifer)。Python呼叫父類別方法的原則是:排在越前面的越優先。所以本情境實際呼叫到的是Hardwoodshow_breed()而不是Conifer的。

  • 假如Timba類別的繼承順序調過來,Conifer放在前面,這時呼叫到的當然就是Conifershow_breed()了。

  • 情境-2:

    class Tree():
        def __init__(self, breed: str):
            self.breed = breed
        def show_breed(self):
            print(f'Tree: {self.breed = :12}')
    
    class Hardwood(Tree):      # 繼承自Tree。
        ...                    # 不實作。
    
    class Conifer(Tree):       # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Conifer: {self.breed = :12}')
    
    class Timba(Hardwood, Conifer):  # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
        ...         # 完全不實作,看效果如何。
    
    
    tree = Timba('timba')
    tree.show_breed()
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221007/201484853mLgp0Jufk.png

  • 這次第一順位的Hardwood並未實作出show_breed(),而第二順位的Conifer有實作,所以呼叫Conifershow_breed()。這個結果應該很好理解。

  • 情境-3:

    class Tree():
        def __init__(self, breed: str):
            self.breed = breed
        def show_breed(self):
            print(f'Tree: {self.breed = :12}')
    
    class Hardwood(Tree):      # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Hardwood: {self.breed = :12}')
    
    class Conifer(Tree):       # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Conifer: {self.breed = :12}')
    
    class Timba(Hardwood, Conifer):  # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
        def show_breed(self):        # 這次有自己的show_breed()。
            print(f'Timba: {self.breed = :12}')
    
    
    tree = Timba('timba')
    tree.show_breed()
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221007/201484855BRAkdFmqE.png

  • 這次Timba實作屬於它自己了show_breed(),結果當然是用自己的。

  • 再說一次繼承體系「方法」呼叫的優先順序:

    • 本類別
    • 第一順位父類別
    • 第二順位父類別
    • ...
    • 第n順位父類別
    • 祖父
    • 曾祖父
    • 高祖父
    • 天祖父
    • ...
    • ...
    • 炎黃二帝(object class)
  • 情境-4:

    class Tree():
        def __init__(self, breed: str):
            self.breed = breed
        def show_breed(self):
            print(f'Tree: {self.breed = :12}')
    
    class Hardwood(Tree):      # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Hardwood: {self.breed = :12}')
    
    class Conifer(Tree):       # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Conifer: {self.breed = :12}')
    
    class Timba(Hardwood, Conifer):  # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
        def show_breed(self):        # 這次有自己的show_breed()。
            print(f'Timba: {self.breed = :12}')
    
    
    tree = Timba('timba')
    tree.show_breed()
    Tree.show_breed(tree)        # 直接呼叫Tree的show_breed(),注意這時必須「手動傳入」物件tree。
    Hardwood.show_breed(tree)    # 直接呼叫Hardwood的show_breed()。
    Conifer.show_breed(tree)     # 直接呼叫Conifer的show_breed()。
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221007/20148485IC1HyVrQiE.png

  • 本情境展示在繼承體系中,(主程式)跳過優先順序原則,直接呼叫父或祖父的方法。注意這時必須「手動傳入」物件(Tree.show_breed(tree))。

  • 情境-5:

    class Tree():
        def __init__(self, breed: str):
            self.breed = breed
        def show_breed(self):
            print(f'Tree: {self.breed = :12}')
    
    class Hardwood(Tree):      # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Hardwood: {self.breed = :12}')
    
    class Conifer(Tree):       # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Conifer: {self.breed = :12}')
    
    class Timba(Hardwood, Conifer):  # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
        def show_breed(self):        # 這次有自己的show_breed()。
            print(f'Timba: {self.breed = :12}')
            Tree.show_breed(self)        # 在這裡呼叫祖父的show_breed(),傳入self。
            Hardwood.show_breed(self)    # 呼叫Hardwood的show_breed()。
            Conifer.show_breed(self)    # 呼叫Conifer的show_breed()。
    
    
    tree = Timba('timba')
    tree.show_breed()
    
  • 也可以在Timba類別的方法內呼叫其父執輩的方法。這次輸出和情境-4完全一樣:
    https://ithelp.ithome.com.tw/upload/images/20221007/20148485IC1HyVrQiE.png

  • 情境-6:

    class Tree():
        def __init__(self, breed: str):
            self.breed = breed
        def show_breed(self):
            print(f'Tree: {self.breed = :12}')
    
    class Hardwood(Tree):      # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Hardwood: {self.breed = :12}')
            Tree.show_breed(self)     # 呼叫其父類別的show_breed()。
    
    class Conifer(Tree):       # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Conifer: {self.breed = :12}')
            Tree.show_breed(self)     # 呼叫其父類別的show_breed()。
    
    class Timba(Hardwood, Conifer):  # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
        def show_breed(self):        # 這次有自己的show_breed()。
            print(f'Timba: {self.breed = :12}')
            Hardwood.show_breed(self)
            Conifer.show_breed(self)
    
    
    tree = Timba('timba')
    tree.show_breed()
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221007/20148485Rp6Zw7lwv4.png

  • 這回改在HardwoodConifer中以Tree.show_breed(self)呼叫。

  • 各位有沒注意到,以上的輸出,Tree.show_breed()實際上跑了兩次(太過細節恕不追蹤)。有沒有辦法讓它只跑一次呢?有的,答案就是下面的情境:

  • 情境-7:

    class Tree():
        def __init__(self, breed: str):
            self.breed = breed
        def show_breed(self):
            print(f'Tree: {self.breed = :12}')
    
    class Hardwood(Tree):      # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Hardwood: {self.breed = :12}')
            super().show_breed()      # 用super()來呼叫其父類別,這時不必傳入self。
    
    class Conifer(Tree):       # 繼承自Tree。
        def show_breed(self):  # 沒有自己的constructor。
            print(f'Conifer: {self.breed = :12}')
            super().show_breed()      # 用super()來呼叫其父類別,這時不必傳入self。
    
    class Timba(Hardwood, Conifer):  # 有兩個老爸,依先後分別是Hardwood和Conifer(次序有意義)。
        def show_breed(self):        # 這次有自己的show_breed()。
            print(f'Timba: {self.breed = :12}')
            super().show_breed()      # 用super()來呼叫其父類別,這時不必傳入self。
    
    
    tree = Timba('timba')
    tree.show_breed()
    
  • 輸出:
    https://ithelp.ithome.com.tw/upload/images/20221007/201484850rkB1kfNbo.png

  • 還記得之前介紹過的super()函數嗎?這次又派上用場了。

  • 改用super().show_breed()呼叫其父類別之後,Tree.show_breed()竟然只跑一次。而且show_breed()方法還不必傳入self,較為方便好用。


  • 今天先寫到這裡。多重繼承其實還有好些細節可以講的,只好留待下一篇再說了。不過也有可能就此打住,明天換另一主題。

上一篇
Overriding
下一篇
The Pros and Cons of Multiple Inheritance
系列文
Oops! OOPP: An Introduction to Object-Oriented Programming in Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言