­

【python系統學習14】類的繼承與創新

目錄:

初中學政治我們就學到過,要繼承中華民族的優秀文化、又要在繼承的基礎上創新。

文化是在不斷繼承和創新中發展的,代碼也是。

我們可以用類特有的繼承方法和拓展創新功能,實現代碼層面的前進。

此節這兩個知識點屬於類中較高階的操作,讓用類寫成的代碼更容易復用、拓展和維護。

類的繼承

說道「繼承」這倆字,你能想到啥?

反正我想到的是,兒子繼承老子的姓氏籍貫特徵和遺產等。

放到代碼里,就是子類繼承父類的屬性和方法。

也就是說,通過類的繼承,可以讓子類擁有父類擁有的所有屬性和方法。
這樣,很多基礎代碼不用再重複書寫,就可以直接實現代碼的復用

子類和父類

那之前只了解到類的概念,怎麼又多出來一個「子類」和「父類」的區分呢?

我想,這只是統稱。凡是一個類A繼承了另一個類B,那麼A就是B的兒子,A就可以統稱為子類,B就可以統稱為父類。

題外話,我現在看這個「類」字看多了,咋看咋像「糞」。。。

繼承的寫法

偽代碼

class A(B): 
  ...子類A的創新定製代碼 # 下詳

注意:

  1. A就是子類,B就是父類
  2. 子類繼承父類時,只需要子類右邊的小括號內部寫父類的類名即可。
  3. 小括號和冒號都是英文格式

繼承示例

還是以老子和兒子的身份,編寫兩段語義化的代碼來了解下:

# 子類Son繼承父類Father的示意
class Father:
  familyName = '郭'
  nativePlace = '河北省'
    
  def language(self):
    print('說中國話')

class Son(Father): # 子類Son繼承父類Father的寫法
  def __init__(self, name):
    self.name = name
  def secondLanguage(self):
    self.language()
    print('學說了英語')

父類可以被無限個子類所繼承

不是一個Son類可以繼承Father,再來十個Son也可以。

class Father:
  familyName = '郭' # 表示姓氏
  nativePlace = '河北省' # 表示籍貫
    
  def language(self): # 表示說母語的能力
    print('說中國話')

class Son(Father): # 子類Son繼承父類Father
  def __init__(self, name):
    self.name = name
  def secondLanguage(self):
    self.language()
    print('學說了英語')

# 父類可以被無限個子類所繼承
class Son2(Father): # 子類Son2繼承父類Father
  def __init__(self):
    print('這裡自己想像點Son2特色的代碼吧,我絞盡腦汁了~')
  def characterFn(self):
    self.language()
    pass
class SonN(Father):
  pass

# ...

子類實例可調用父類屬性和方法

子類繼承來的屬性和方法,也會傳遞給子類創建的實例。

這很好理解,子類繼承父類的屬性和方法,相當於子類外掛了父類的屬性和方法。那麼子類的實例,也就可以用這些屬性和方法。

看個例子:

class Father:
  familyName = '郭' # 表示姓氏
  nativePlace = '河北省' # 表示籍貫
    
  def language(self): # 表示說母語的能力
    print('說中國話')

class Son(Father): # 子類Son繼承父類Father
  def __init__(self, name):
    self.name = name
  def secondLanguage(self):
    self.language()
    print('學說了英語')

son1 = Son('小菊')
print(son1.name + '姓' + son1.familyName) 
print(son1.name + '籍貫是' + son1.nativePlace)

# 打印結果:
# 小菊姓郭
# 小菊籍貫是河北省

實例化後的對象son1不僅有類Son內部定義的屬性,還有父類Father內部定義的屬性familyName、nativePlace等。

說明子類創建的實例,從子類那間接得到了父類的所有屬性和方法,實例化對象可以隨便運用。

類的始祖(根類)

根類 – object

在上邊類的繼承中,我們協議子類是Son、父類是Father。

那Father還有他的父類嗎?

答案是有的,那就是object

object是所有類的父類,我們將其稱為根類(可理解為類的始祖)。

上述代碼中,class Father:相當於寫成class Father(object)

用isinstance來判斷說明:

實例歸屬判斷 – isinstance()

函數isinstance(),可以用來判斷某個實例是否屬於某個類。

觀察代碼看用法:

# 實例歸屬判斷 isinstance()
class Father:
  pass
class Son(Father):
  pass
class Son2:
  pass

son1 = Son()

print(1,isinstance(son1,Son)) # True
# 說明:子類創建的實例屬於子類(廢話。。。)

print(2,isinstance(son1,Father)) # True
# 說明:子類創建的實例同時也屬於父類

print(3,isinstance(son1,object)) # True
# 說明:說明任何類創建的實例都屬於根類object

print(4,isinstance(son1,(Son,Father))) # True
# 說明:son1屬於Son的實例、也屬於Father的實例

print(4,isinstance(son1,(Father,Son))) # True
# 說明:son1屬於Son的實例、也屬於Father的實例

print(5,isinstance(son1,Son2)) # False
# 說明:son1不是Son2的實例。A子類創建的實例不屬於C子類

print(6,isinstance(Son,Father)) # False
# 說明:這倆都是類所以報錯。

print(7,isinstance(Son,object)) # True
# 說明:可以說明Son(任何類)是根類object的實例

print(8,isinstance(Father,object)) # True
# 說明:可以說明Father(任何類)是根類object的實例

print(9,isinstance(Son,(Father,object))) # True
# 說明:Son不屬於Father的實例,但他是根類object的實例

總結用法:

# 第一種(偽代碼)
isinstance(實例名, 類名)

# 第二種(偽代碼)
isinstance(實例名, (類名1, 類名2...)) # 第二個參數是類名組成的元祖類型數據。

返回結果:
布爾值。True或False。
判斷第一個參數是第二個參數的實例、或者判斷第一個參數屬於第二個參數中某個類的實例,則返回True。反之返回False。

總結知識點:

1. 子類創建的實例,同屬於該子類的父類。
2. 父類創建的實例,與他的子類沒關係。
3. 所有類,都屬於根類object的實例。
4. 所有實例,都屬於根類object的實例。
5. 子類A創建的實例,不屬於其他C等子類的實例。

具體值與基本類型的歸屬判斷:
看代碼總結知識點吧

print(isinstance(1,int)) # True
# 判斷1是否為整數類的實例,實例1是整數類的實例

print(isinstance(1,str)) # False
# 實例1不是字符串類的實例

print(isinstance(1,(int,str))) # True
# 實例1是整數類的實例

print(isinstance(1,object)) # True
# 實例1是根類object的實例

類的繼承升級版 – 多層繼承

1、啥是多層繼承

繼承不僅可以發生在兩個層級之間(即父類-子類),還可以有父類的父類、父類的父類的父類……

比如三個類A、B、C。A是爺爺、B是爸爸、C是兒子。我們可以B繼承A、C再繼承B。後邊再可以有孫子D、D再繼承C等等。。。
這樣一層一層,層層向上/向下繼承,就是多層繼承

一「碼」以蔽之:

2、偽代碼

class B(A):
  ...
class C(B):
  ...
class D(C):
  ... 
class...

3、示例代碼

# 多層繼承
class Grand:
  familyName = '郭' # 表示姓氏
class Father(Grand):
  fatherName = '明爸'
 
class Son(Father):
  def __init__(self,name):
    self.sonName = name
    print(self.fatherName) # 子類拿到父類的屬性
    print(self.familyName+self.sonName) # 子類拿到父類的父類 的屬性

son = Son('小明')
# 執行後打印
# 明爸
# 郭小明

print(son.fatherName) # 實例拿到父類的屬性
print(son.familyName + son.sonName) # 實例拿到父類的父類 的屬性
# 執行後打印
# 明爸
# 郭小明

只要你願意,你可以繼續拓展上面的例子,或往上(爺爺的爸爸),或往下(兒子的兒子)。所以,多層繼承又屬於繼承的深度拓展。

4、多層繼承的好處

從上邊代碼就能看出功能,Son類創建的實例不僅可以調用Father的屬性和方法、還能調用爺爺Grand類的屬性和方法。

結論就是:子類創建的實例可調用所有層級父類的屬性和方法。

類的繼承升級版 – 多重繼承

1、啥是多重繼承

一個類同時繼承多個類,就叫多重繼承。就好像同時擁有好幾個爸爸。

# 偽代碼:
class A(B,C,D):

2、就近繼承

括號里B、C和D的順序是有講究的。和子類更相關的父類會放在更左側。

也就是血緣關係越深的爸爸,在括號里的順序越靠前。

既然跟B爸爸最親,所以兒子A類創建的實例在調用屬性和方法時,會優先在最左側的父類B中找,找不到才會去第二左側的父類C中找,依舊找不到才會最後去父類D中找。有點「就近原則」的意思,當然,如果最後幾個類中都沒找到,

3、爸爸近還是爺爺近

A類創建的實例調用屬性和方法時,先在直接爸爸A類中找,還是先在爸爸繼承的最親近的B類中找?(這裡A相當於A實例的爸爸,B相當於A實例的爺爺。)

class Grand:
  name = '親爺爺'
class Father(Grand):
  name = '爸爸'

son = Father()
# 說明先在直接爸爸中找屬性
print(son.name) # 爸爸

總結:先在直接爸爸中找屬性。找不到,再去爸爸繼承的爺爺類們找。

4、多重繼承的示例

用一段大型家庭倫理來寫一個多重繼承。

其中,身份情況如下:

代表的身份 倫理身份 對應下邊示例中的變量
實例 兒子 son
A類 爸爸 Father
B類 親爺爺 爺爺Grand2在三個爺爺中排行老二
C類 大爺爺 Grand1
D類 小爺爺 Grand3
# 多重繼承
class Grand1:
  name = '大爺爺'
  grand1 = '我是老大'
  age = 60
class Grand2:
  name = '親爺爺'
  grand2 = '我是老二'
  age = 59
class Grand3:
  name = '小爺爺'
  grand3 = '我是老三'
  age = 58
  hobby = '只有小爺爺有hobby屬性'
class Father(Grand2, Grand1, Grand3):
  name = '爸爸'
  father = '我是爸爸'

son = Father()

# 說明先在直接爸爸中找屬性
print(son.name) # 爸爸

# 以下說明實例可以拿到其他重繼承類內部的屬性
print(son.father) # 我是爸爸
print(son.grand1) # 我是老大
print(son.grand2) # 我是老二
print(son.grand3) # 我是老三

# 以下說明多重繼承的就近原則 - 就近取最近的爺爺的屬性
print(son.age) # 59

# 以下說明前幾重父類都沒有,取第一個有該屬性的父類,哪怕這個父類時最後一重繼承的
print(son.hobby) # 只有小爺爺有hobby屬性

5、多重繼承的作用

這自然不用說,子類創建的實例可調用所有重父類的屬性和方法。

多層繼承和多重繼承

二者的比較

來一個比較表格吧

名稱 多層繼承 多重繼承
寫法 class B(A):

class C(B):
class A(B, C, D):
例子 孫子繼承兒子、兒子繼承爸爸、爸爸繼承爺爺 爸爸繼承爺爺、大爺爺、小爺爺
特點 類在縱向上深度拓展 類在橫向上寬度擴展
作用 子類創建的實例,可調用所有層級的父類的屬性和方法 同多層繼承,可以調用所有。不過遵循就近原則。優先考慮靠近子類的父類的屬性和方法。

二者的結合

多層繼承和多重繼承二者單用都是一條線,但是將二者結合起來,那就是個十字啊!
多層繼承和多重繼承結合
實例兒子依次繼承了爸爸、爺爺、大爺爺、小爺爺、爺爺的爸爸這幾個類的所有屬性和方法。簡直就是超級富二代啊~

多層繼承和多重繼承的結合,讓繼承的類擁有更多的屬性和方法,且能更靈活地調用。進而,繼承的力量也得以放大了很多倍。
多層繼承和多重繼承結合

一碼以蔽之

# 多層繼承和多重繼承結合
class GrandFather:
  name = '爺爺的爸爸-太爺爺'
  age = 102
class Grand1:
  name = '大爺爺'
class Grand2(GrandFather): # 多層繼承1
  name = '親爺爺'
class Grand3:
  name = '小爺爺(又名二爺爺)'
  age = 62
class Father(Grand2, Grand1, Grand3): # 多層繼承2 + 多重繼承
  name = '爸爸'

son = Father() # 實例兒子
print(son.name) # 爸爸
print(son.age) # 102

上例中,son.age這段打印的 102說明,雖然Grand2中沒有,但是Grand2的多層繼承GrandFather中有,所以順序是先從多層中找,最後才考慮多重繼承的Grand3中的變量。

總結:多重繼承中,若某父類還有父類的話,會先按照多層繼承的順序,縱嚮往上找到頂。若到頂還沒有,則再繼續向右擴展尋找多重的繼承。

類的創新

我們可以在繼承父類代碼的基礎上,再書寫子類自己更具自我特色的代碼。這就是子類的創新部分了。

比如下邊代碼中,兒子的姓就是繼承父親的。兒子的名字就是自己創新的部分。兒子籍貫是繼承父類後生下來就有的,但是以後自己跑到北京、杭州居住,那就是自己創新的部分。

創新 – 新增代碼

# 子類繼承父類並做自我創新
class Father:
  familyName = '郭' # 姓氏
  nativePlace = '河北省' # 籍貫
  def language(self):
    print('%s家,母語說中國話' %(self.familyName))

class Son(Father): # 子類Son繼承父類Father
  def __init__(self, name, presentAddress):
    self.name = name # 子類創新自己的屬性name
    self.presentAddress = presentAddress # 子類創新自己的屬性presentAddress

  def secondLanguage(self, languageName): # 子類創新自己的方法secondLanguage
    self.language()
    print('%s單獨學說了%s' %(self.name, languageName))
    
  def resume(self): # 子類創新自己的方法resume
    print('%s姓%s,籍貫是%s。現居住在%s' %(self.name, self.familyName,self.nativePlace, self.presentAddress))

# 子類的第一個實例
son1 = Son('小菊''北京')
son1.secondLanguage('英語')
son1.resume()

# 子類的第二個實例
son2 = Son('小鋒''杭州')
son2.secondLanguage('韓語')
son2.resume()

# 實例既可以用子類的屬性和方法,也可以調用父類的屬性和方法
print(son1.familyName)
print(son1.nativePlace)

上述代碼中,
子類內部用的self.language()、以及這個language方法內部用的屬性familyName、子類自己方法resume中用的self.nativePlace等都是繼承自父類Father的。
這就是繼承的部分。

Son類中的self.name、self.presentAddress 是屬於子類新增的屬於自己的屬性、
self.secondLanguage、self.resume 是屬於子類新增的屬於自己的方法。這就是創新的部分。

好吧,我承認這是一句多餘的不行的廢話

創新 – 修改(重寫)繼承的內容

在繼承的基礎上可以做新加代碼,甚至可以重寫(修改)繼承來的屬性和方法。

重寫代碼: 是在子類中,對父類代碼的修改。

還是用代碼來說明問題,在上邊代碼的基礎上,我們想一段情景劇:

Father類現在有一個二兒子Son2,他從小生下來被過繼給了日本的親叔叔。
因為叔叔和爸爸同姓,所以繼承的屬性「familyName」不用管,還是姓「郭」。但是到了日本後上戶口就是日本的籍貫了,所以「nativePlace」得修改重寫。
另外雖然是爸爸的兒子(繼承自Father),但是因為在日本長大,所以母語變成說日語了。於是我們重寫了繼承來自爸爸類的方法「language」。如下

class Father:
  familyName = '郭'
  nativePlace = '河北省'
  def language(self):
    print('%s家,母語說中國話' %(self.familyName))

class Son(Father): # 子類Son繼承父類Father
  # ...代碼同上一段里的

class Son2(Father): # 繼承父類Father,不過被過繼給叔叔的二兒子類Son2
  languageTxt = '日語' # 自己的屬性,屬新增的創新
  nativePlace = '北海道' # 修改的屬性,屬重寫的創新
  def language(self): # 修改的方法,屬重寫的創新
    print('我的母語說:', self.languageTxt) # 裡邊的代碼有自己定製的內容,屬於重寫。
  
son2 = Son2()
son2.language()
# 我的母語說: 日語

可見,重寫父類的屬性和方法,只要在子類中重新定義同名的變量,並做自己的定製代碼即可。

但是,直接在Son2里這麼寫,Father會不會傷心呢?
到底是自己生的兒子,給了別人後就這麼迫不及待、直接的修改,爸爸的心,痛啊!

所以,重寫代碼按照上邊這麼寫,他粗魯了。那有什麼優雅的重寫嗎?

優雅的重寫

# 優雅的重寫
class Father:
  familyName = '郭'
  nativePlace = '河北省'
  def language(self, languageTxt = '中國話'): # 需要父類的這個方法協助修改
    print('%s家,母語說%s' %(self.familyName, languageTxt)) 

class Son2(Father):
  languageTxt = '日語'
  nativePlace = '北海道'
  def language(self): # 依舊重寫方法
    Father.language(self, self.languageTxt) # 不過裡邊的代碼不再是粗魯的直接修改為自己的內容,而是調用父類的方法,但是傳不同的參數。
  
son2 = Son2()
son2.language()
# 郭家,母語說日語

上邊代碼,依舊重寫方法。不過裡邊的代碼不再是粗魯的直接修改為自己的內容,而是調用父類的方法,但是傳不同的參數。


總結:所謂創新,在復用代碼的基礎上,又能滿足個性化的需求。

本文使用 mdnice 排版