課時38:類與物件:繼承

那是個好男孩發表於2018-08-23

目錄:
  一、繼承

  二、呼叫未繫結的父類方法

  三、使用super函式

  四、多重繼承

  五、課時38課後習題及答案

 

現在需要擴充套件遊戲,對魚類進行細分,有金魚(Goldfish)、三文魚(Salmon)、鯉魚(Carp),還有鯊魚(Shark)。那麼我們來思考一個問題:能不能不要每次都從頭到尾去重新定義一個新的魚類呢?因為我們知道大多數魚的屬性和方法是相似的,如果有一種機制可以讓這些相似的東西得以自動傳遞,那就方便快捷多了。這種機制就是今天要講的:繼承。

***********

一、繼承

***********

 語法很簡單:

class 類名(被繼承的類):
        ...

被繼承的類稱為基類、父類或超類;繼承者稱為子類,一個子類可以繼承它的父類的任何屬性和方法。舉個例子:

>>> class Parent:
    def hello(self):
        print("正在呼叫父類的方法...")

        
>>> class Child(Parent):
    pass

>>> p = Parent()
>>> p.hello()
正在呼叫父類的方法...
>>> c = Child()
>>> c.hello()
正在呼叫父類的方法...

需要注意的是:如果子類中定義與父類同名的方法或屬性,則會自動覆蓋父類對應的方法或屬性:

>>> class Child(Parent):
    def hello(self):
        print("正在呼叫子類的方法...")

        
>>> c = Child()
>>> c.hello()
正在呼叫子類的方法...

好,那嘗試一下剛才提到的金魚(Goldfish)、三文魚(Salmon)、鯉魚(Carp),還有鯊魚(Shark)的例子:

#p11-2.py
import random as r

class Fish:
      def __init__(self):
            self.x = r.randint(0, 10)
            self.y = r.randint(0, 10)
            
      def move(self):
            #這裡主要演示類的繼承機制,就不考慮檢查場景邊界和移動方向的問題

            #假設所有魚都是一路向西遊
            self.x -= 1
            print("我的位置是:", self.x, self.y)

class Goldfish(Fish):
      pass

class Carp(Fish):
      pass

class Salmon(Fish):
      pass

#上邊幾個都是食物,食物不需要有個性,所以直接繼承Fish類的全部屬性和方法即可
#下邊定義鯊魚類,這個是吃貨,除了繼承Fish類的屬性和方法,還要新增一個吃的方法

class Shark(Fish):
      def __init__(self):
            self.hungry = True

      def eat(self):
            if self.hungry:
                  print("吃貨的夢想就是天天有的吃^_^")
                  self.hungry = False
            else:
                  print("太撐了,吃不下了!")
>>> #先執行p11-2.py
>>> fish = Fish()
>>> #試試小魚兒能不能動
>>> fish.move()
我的位置是: 7 0
>>> goldfish = Goldfish()
>>> goldfish.move()
我的位置是: 2 2
>>> goldfish.move()
我的位置是: 1 2
>>> goldfish.move()
我的位置是: 0 2
>>> #可見金魚確實在一路向西
>>> #下面嘗試生成鯊魚
>>> shark = Shark()
>>> #試試這貨能不能吃東西?
>>> shark.eat()
吃貨的夢想就是天天有的吃^_^
>>> shark.eat()
太撐了,吃不下了!>>> shark.move()
Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    shark.move()
  File "C:\Users\14158\Desktop\lalallalalal.py", line 13, in move
    self.x -= 1
AttributeError: 'Shark' object has no attribute 'x'

奇怪!同樣是繼承於Fish類,為什麼金魚(goldfish)可以移動,而鯊魚(shark)一移動就報錯呢?

其實這裡丟擲的異常說得很清楚了:Shark物件沒有x屬性。原因其實是這樣的:在Shark類中,重寫了魔法方法_ _init_ _,但新的_ _int_ _方法裡邊沒有初始化鯊魚的x座標和y座標,因此呼叫move方法就會出錯。那麼解決這個問題的方案就很明顯了,應該在鯊魚類中重寫_ _int_ _方法的時候先呼叫基類Fish的_ _init_ _方法。

下面介紹兩種可以實現的技術:

(1)呼叫未繫結的父類方法

(2)使用super函式

 

*******************************

二、呼叫未繫結的父類方法

*******************************

 看以下程式碼:

class Shark(Fish):
      def __init__(self):
            Fish.__init__(self)
            self.hungry = True

再執行下,發現鯊魚可以移動了:

>>> #先執行修改後的p11-2.py
>>> shark = Shark()
>>> shark.move()
我的位置是: 6 0
>>> shark.move()
我的位置是: 5 0
>>> shark.move()
我的位置是: 4 0

這裡需要注意的是這個self並不是父類Fish的例項物件,而是子類Shark的例項物件,所以這裡說的未繫結是指並不需要繫結父類的例項物件,使用子類的例項物件代替即可。

在Python中有一個更好的方案可以代替它,就是使用super函式。

 

**********************

三、使用super函式

**********************

 super函式能夠幫我們自動找到基類的方法,而且還為我們傳入了self引數,這樣就不需要做這些事了:

class Shark(Fish):
      def __init__(self):
            super().__init__()
            self.hungry = True

執行後會得到同樣的結果。

super函式的“超級”之處在於你不需要明確給出任何基類的名字,它會自動幫你找到所有基類以及對應的方法。由於你不用給出基類的名字,這就意味著如果需要改變類繼承關係,只要改變class語句裡的父類即可,而不必要在大量程式碼中去修改所有被繼承的方法。

 

****************

四、多重繼承

****************

 除此之外Python還支援多繼承,就是可以同時繼承多個父類的屬性和方法:

class 類名(父類1,父類2,父類3,...):
        ...
>>> class Base1:
    def foo1(self):
        print("我是foo1,我在Base1中...")

        
>>> class Base2:
    def foo2(self):
        print("我是foo2,我在Base2中...")

        
>>> class C(Base1,Base2):
    pass

>>> c = C()
>>> c.foo1()
我是foo1,我在Base1中...
>>> c.foo2()
我是foo2,我在Base2中...

上面就是基本的多重繼承的語法。但多重繼承其實很容易導致程式碼混亂,所以當你不確定是否真的必須使用多重繼承的時候,請儘量避免使用它,因為有些時候會出現不可預見的BUG。

 

【擴充套件閱讀】多重繼承的陷阱:磚石繼承(菱形繼承)問題(https://fishc.com.cn/forum.php?mod=viewthread&tid=48759&extra=page%3D1%26filter%3Dtypeid%26typeid%3D403).

*******************************

五、課時38課後習題及答案

*******************************

 

 

相關文章