目錄:
一、繼承
二、呼叫未繫結的父類方法
三、使用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課後習題及答案
*******************************