1. 程式人生 > >【python系統學習14】類的繼承與創新

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

目錄:
  • 目錄: [toc]
  • 類的繼承
    • 子類和父類
    • 繼承的寫法
    • 繼承示例
    • 父類可以被無限個子類所繼承
    • 子類例項可呼叫父類屬性和方法
  • 類的始祖(根類)
    • 根類 - object
    • 例項歸屬判斷 - isinstance()
  • 類的繼承升級版 - 多層繼承
    • 1、啥是多層繼承
    • 2、虛擬碼
    • 3、示例程式碼
    • 4、多層繼承的好處
  • 類的繼承升級版 - 多重繼承
    • 1、啥是多重繼承
    • 2、就近繼承
    • 3、爸爸近還是爺爺近
    • 4、多重繼承的示例
    • 5、多重繼承的作用
  • 多層繼承和多重繼承
    • 二者的比較
    • 二者的結合
  • 類的創新
    • 創新 - 新增程式碼
    • 創新 - 修改(重寫)繼承的內容
    • 優雅的重寫

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

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

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

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

類的繼承

說道“繼承”這倆字,你能想到啥?

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

放到程式碼裡,就是子類繼承父類的屬性和方法。

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

子類和父類

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

我想,這只是統稱。凡是一個類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 排版