小牛叔帶你輕松飛越Python類的門檻
1. 大話繼承
繼承最好的示例竟然是病毒復(fù)制。類似于COVID-19病毒全球肆虐,病毒復(fù)制變異的過程就是下一代繼承上一代部分特性,并發(fā)展出新特性的過程(如下圖)。
病毒的變異來源于DNA(RNA)蛋白質(zhì)突變因此編程中的繼承,也具有如下兩個(gè)特征:
- 復(fù)制上一代的特性(即屬性與方法)
- 發(fā)展出新特性(即屬性與方法)
2. 層次性與復(fù)用
可以把類Class看成病毒(代碼)的DNA,那么定義新的類(Class)就相當(dāng)于產(chǎn)生了新病毒,而從類創(chuàng)建實(shí)例的過程,就類似于同病毒自我復(fù)制產(chǎn)生很多DNA相同的病毒體, 類的繼承就相當(dāng)于這個(gè)DNA在復(fù)制過程中產(chǎn)生了變異,產(chǎn)生了新的種類的病毒DNA或是病毒的變種的DNA的過程。
“繼承”過程在程序中通常必須由程序來手工的顯式聲明(當(dāng)然也不排除能自我復(fù)制變異的程序存在:-) ),假設(shè)我們正在給類似于王者榮耀的游戲做NPC的角色,為了重復(fù)利用角色的特性,我們可以把各種角色以層次關(guān)系進(jìn)行組織,圖中第個(gè)節(jié)點(diǎn)都是類:
游戲角色繼承關(guān)系
在這樣的繼承關(guān)系里,所有的NPC都可以共享相同的底層邏輯:共享各種屬性比如生命值,攻擊能力(值是不同的但是名稱相同);并且可以共享相同的行為(即方法)比如攻擊,無論什么NPC攻擊了就可以讓對(duì)方減少生命值。
看上圖的第二層,小兵繼承自NPC,并且可以發(fā)展出自己的特性給下一層的Class重復(fù)使用。比如小兵可以增加“行走”這個(gè)行為(即方法),這個(gè)方法可以讓小兵沿著兵線路徑進(jìn)行移動(dòng),無論什么兵都要共用這個(gè)行為。
3. 基本示例
下面我們就要實(shí)現(xiàn)上圖兩個(gè)類: NPC和Soldier,其關(guān)系如下圖所示:
類中的各元素的繼承關(guān)系下面的代碼實(shí)現(xiàn)上面的類的繼承,先定義最基本的NPC類和Soldier類如下:
1 class Npc: 2 3 def __init__(self,name): #初始化方法 self(方法的第1個(gè)位置參數(shù))代表這個(gè)類的實(shí)例 4 self.name = 'NPC' #初始名稱為NPC 5 self.life = 100 #生命值初始為100 6 self.harm = 2 #傷害初始為2 7 8 def __str__(self): #實(shí)例轉(zhuǎn)成字符串時(shí)返回的字符串值 9 return '%s %d'%(self.name,self.life) #返回這個(gè)類的實(shí)例時(shí)就打印名稱和生命值 10 11 def attack(self,other): #攻擊使別人生命值下降,把“別人”(other)當(dāng)成參數(shù)傳進(jìn)來 12 print(self.name,'攻擊',other.name,'-%d'%self.harm) 13 other.life -= self.harm 14 15 16 class Soldier(Npc): 17 def __init__(self,name): 18 super().__init__(name) #調(diào)用上層的初始化函數(shù),使用父類的初始化代碼 19 self.name = name #士兵的名稱 20 21
?
從Npc類的定義(參考前文介紹的類的定義)分析,明顯可以看到這個(gè)類有3個(gè)屬性分別是life(生命值)、harm(傷害力)和name(名稱)并且有攻擊行為(方法)。
繼承語法:子類士兵定義時(shí)直接在類名士兵(Soldier)后使用括號(hào)即可,該類沒有產(chǎn)生新的屬性,只是使用傳入的名稱覆蓋了原來的name屬性。大家注意如下的表達(dá)式:
super().__init__(name) #調(diào)用上層的初始化函數(shù),使用父類的初始化代碼
?
表達(dá)式super()指的是父類的列表,這個(gè)語句就是調(diào)用父類Npc的初始化函數(shù),這就顯式地繼承了NPC的3個(gè)屬性:life(生命值)、harm(傷害力)和name(名稱)。在其它情況下如果父類還有父類就會(huì)以MRO的順序來排列。下面有文章是專門講MRO的,可以參考:
4.重用父類方法
在上面Soldier類中沒有定義attack(攻擊)方法,而attack是定義在其父類Npc中的,那么我們可不可以直接在子類中使用呢?添加并運(yùn)行如下的代碼:
soldier_a = Soldier('紅方兵') #實(shí)例化士兵a soldier_b = Soldier('藍(lán)方兵') #實(shí)例化士兵b soldier_a.attack(soldier_b) #使用了父類attack的方法 print(soldier_a,soldier_b) #打印攻擊的效果
?
上述代碼在游戲當(dāng)中產(chǎn)生了紅藍(lán)兩方的士兵,并且通過初始化方法傳入了名稱,并且通過實(shí)例調(diào)用了attack方法,如果這個(gè)方法起作用的話,那么其中被攻擊的藍(lán)方兵應(yīng)該生命值下降。把整個(gè)程序一起運(yùn)行,看到如下的結(jié)果:
紅方兵 攻擊 藍(lán)方兵 -2
紅方兵 100 藍(lán)方兵 98
可以看到無需要任何設(shè)置,子類可以直接使用父類的方法,但是子類必須通過重構(gòu)__init()__這個(gè)函數(shù),并且在函數(shù)中使用super()方法來,創(chuàng)建父類通過_init()__創(chuàng)建的實(shí)例屬性。
5. 重構(gòu)其它方法
Soldier子類完全重用了父類的attack方法,實(shí)際上也重構(gòu)了父類的__init()__函數(shù),以獲得父類的屬性。你作為游戲設(shè)計(jì)者,覺得這種“攻擊”太普通了,想設(shè)計(jì)更酷炫的“攻擊”,這就需要通過重構(gòu)attack方法,來增加新的特性。
為了讓讀者更加了解方法的重構(gòu),我們假設(shè)“超級(jí)兵”自帶“反甲”,即在受到攻擊時(shí)自己受傷的同時(shí),會(huì)讓對(duì)方損失生命值。我們通過繼承Soldier類來看看這個(gè)“超級(jí)兵”的attack怎么寫?從NPC -> 兵 -> 超級(jí)兵 ,繼承的關(guān)系如下圖:
多級(jí)繼承類中各元素的繼承關(guān)系繼續(xù)添加并運(yùn)行如下的代碼:
class SuperSoldier(Soldier): def __init__(self,name): self.armHarm = 1 #反甲傷害 super().__init__(name) #調(diào)用上層的初始化函數(shù),使用父類的初始化代碼 def attack(self,other): super().attack(other) #調(diào)用上層的攻擊函數(shù),重用父類的普通攻擊行為 if other.armHarm: self.life -= other.armHarm print(other.name, '有反甲受傷:',other.armHarm) super_a = SuperSoldier('超級(jí)兵A') #實(shí)例化帶反甲的超級(jí)兵 super_b = SuperSoldier('超級(jí)兵B') #實(shí)例化帶反甲的超級(jí)兵 super_b.attack(super_a) #超級(jí)B攻擊A print(super_a,super_b)
?
上述代碼給超級(jí)兵增加了反甲傷害的屬性,并且重寫了attack代碼,運(yùn)行后,結(jié)果如下:
超級(jí)兵B 攻擊 超級(jí)兵A -2
超級(jí)兵A 有反甲受傷: 1
超級(jí)兵A 98 超級(jí)兵B 99
可以看出超級(jí)兵B雖然攻擊了A但是自己也受了1點(diǎn)的傷害。通過在類中對(duì)父類定義的函數(shù)進(jìn)行重構(gòu),并且結(jié)合super()函數(shù),我們即可以重用老特性也可以為下一代增加新特性。
6. 多類繼承
多類繼承即有多個(gè)父類,這樣就可以綜合多個(gè)類的特性。 在王者里,已方戰(zhàn)勝大龍,在我方兵線里會(huì)產(chǎn)生一種生物叫主宰先鋒,如下所示它即有著兵的通常屬性比如走路路線、生命值、攻擊力,也有著龍(野怪)的噴火攻擊方式。
多類繼承能實(shí)現(xiàn)合體的效果新建文件,復(fù)制粘貼下面全部的代碼:
class Npc: def __init__(self,name): #初始化方法 self(方法的第1個(gè)位置參數(shù))代表這個(gè)類的實(shí)例 self.name = 'NPC' #初始名稱為NPC self.life = 100 #生命值初始為100 self.harm = 2 #傷害初始為2 def __str__(self): #實(shí)例轉(zhuǎn)成字符串時(shí)返回的字符串值 return '%s %d'%(self.name,self.life) #返回這個(gè)類的實(shí)例時(shí)就打印名稱和生命值 def attack(self,other): #攻擊使別人生命值下降,把“別人”(other)當(dāng)成參數(shù)傳進(jìn)來 print(self.name,'攻擊',other.name,'-%d'%self.harm) other.life -= self.harm class Soldier(Npc): def __init__(self,name): super().__init__(name) #調(diào)用上層的初始化函數(shù),使用父類的初始化代碼 self.name = name #士兵的名稱 def move(self): print(self.name,'走路','.'*20) #用打出一個(gè)點(diǎn)代表移動(dòng) class Beast(Npc): def __init__(self,name): super().__init__(name) #調(diào)用上層的初始化函數(shù),使用父類的初始化代碼 self.name = name def fire(self): print(self.name,'噴火','*'*20) class Pioneer(Soldier,Beast): def __init__(self,name): super().__init__(name) #調(diào)用上層的初始化函數(shù),使用父類的初始化代碼 p = Pioneer('主宰先鋒1') print(p) p.move() p.fire()
?
運(yùn)行之后看到運(yùn)行結(jié)果:
主宰先鋒1 100
主宰先鋒1 走路 ....................
主宰先鋒1 噴火 ********************
通過繼承兩個(gè)類,這個(gè)主宰先鋒即有了走路的功能(只在父類Soldier中有定義),也有了噴火的功能(只在父類Beast中有定義),并且具有最原始的NPC的各種屬性。
以上就是本篇類對(duì)象繼承的內(nèi)容,類的最核心的概念還是復(fù)用,一般高手寫程序時(shí)都是從最抽象的類的開始寫,把最容易共用的代碼先寫好,自頂向下的寫程序。
小牛叔寫文畫畫都不易,記得點(diǎn)贊。文章來源:http://www.zghlxwxcb.cn/news/detail-768252.html
本節(jié)的繼承,如果您看得開心,記得分享給其它小朋友哦。文章來源地址http://www.zghlxwxcb.cn/news/detail-768252.html
到了這里,關(guān)于Python趣味入門14:類的繼承的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!