面向對象有3大特性:繼承、多態、封裝,本章介紹 Python中的 繼承 特性~
算法
繼承是建立類的一種方式,在 Python中支持多繼承,即在建立類的時候能夠繼承一個或者多個父類。
在繼承關係中,被繼承的類一般稱爲父類(或超類,基類),新建的類則稱爲子類(或派生類)。
繼承的優點在於能夠有效地重用代碼,提升代碼的可讀性~
繼承示例:ide
class Fu_1: # 父類 pass class Fu_2: # 父類 pass class Zi_1(Fu_1): # 單繼承 pass class Zi_2(Fu_1, Fu_2): # 多繼承 pass
上述示例中,Fu_1 和 Fu_2 沒有繼承任何類,在 Python3 中,這樣就會默認繼承object類,而在 Python2 中,默認不會繼承 object類,注意區分 ~
可經過 類的內置屬性 __bases__ 查看這個類 繼承的全部父類函數
print(Zi_1.__bases__) print(Zi_2.__bases__) # 輸出結果: (<class '__main__.Fu_1'>,) (<class '__main__.Fu_1'>, <class '__main__.Fu_2'>)
在開發過程當中,若新建的一個類和已建立的另外一個類 屬性及方法大體相同,則可讓新建的類(子類)繼承已建立的類(父類),這樣子類會繼承父類的全部屬性,包括數據屬性和函數屬性,實現了代碼重用,代碼變得簡潔,能夠有效縮短開發週期~code
class Father: def __init__(self, name, age): self.name = name self.age = age def say(self): print('Hello !') class Son(Father): pass p = Son('baby', 19) p.say() # 輸出結果: Hello !
在繼承過程當中,子類也能夠添加或者從新定義這些屬性,當父類和子類中有同名的屬性時(包括數據屬性和函數屬性),會先調用子類中的屬性(操做的是子類的實例化對象)對象
class Father: city = 'NB' def __init__(self, name, age): self.name = name self.age = age def say(self): print('Hello ! ' + self.city) class Son(Father): city = 'HZ' def say(self): print('你好 ~ ' + self.city) def eat(self): pass p = Son('baby', 19) p.say() # 輸出結果: 你好 ~ HZ
在子類的函數中,如果要重用父類中某個函數的功能,能夠直接經過 super 來調用父類中的函數,固然也能夠經過 類名.func() 來調用,只不過這樣與調用普通函數無異。這個常常使用在須要對父類的同名方法進行擴展的場景~blog
class Father: def say(self): print('Hello !') def introduce(self): print('Father') class Son(Father): def say(self): super().say() # Father.say(self) # 經過 類名.func(),輸出結果一致 print('你好 ~') p = Son() p.say() # 輸出結果 Hello ! 你好 ~
上述示例中,使用 super的時候省略了2個參數:Son(當前類名稱,注意不是父類),self(當前對象)繼承
super().say() # 等同於 super(Son, self).say()
因爲 super 方法中已經默認傳遞了self參數,因此後面的函數不須要再次傳遞self~
注意:super關鍵字只在新式類中有,Python3中全部的類都是新式類...
在子類的函數中使用super方法,不必定僅調用同名的父類函數,也能夠調用其餘的父類函數~ip
def say(self): super().introduce() print('你好 ~')
以下示例中子類對父類的 init方法 進行了擴展,這是一種較爲經常使用的使用方式~ci
class Father: def __init__(self, name, age): self.name = name self.age = age def say(self): print('Hello !') class Son(Father): def __init__(self, name, age, hobby): super().__init__(name, age) self.hobby = hobby def say(self): super().say() print('你好 ~')
super方法不光能夠在類的內部使用,也能夠在類的外部的使用,在類的外部使用時,super方法不能夠省略參數開發
# 外部使用super p = Son() super(Son, p).say() # 輸出結果: Hello !
上面已經說過,Python3 中全部的類都是新式類,新建的類沒有繼承任何類的時候,會默認繼承 object 類。
在 Python2中,經典類和新式類並存,新建的類如果沒有繼承任何類,則這個類爲經典類,只有顯示地繼承了 object 類或其子類,這個類纔是新式類~
# Python2中 class C1: # 經典類 pass class C2(C1): # 經典類 pass class C3(object): # 新式類 pass # Python3中 class N1: # 新式類 pass class N2(N1): # 新式類 pass class N3(object): # 新式類 pass
在Python中支持多繼承,新式類和經典類的繼承順序有所差別,如下以鑽石繼承爲例給出示例:
class A(object): def fun(self): print('from A') class B(A): def fun(self): print('from B') class C(A): def fun(self): print('from C') class D(B): def fun(self): print('from D') class E(C): def fun(self): print('from E') class F(D, E): # def fun(self): # print('from F') pass
上述多個類的繼承關係以下圖所示:
當前環境爲Python3,即新式類,可經過內置的__mro__方法查看繼承順序:
f1 = F() # f1.fun() print(F.__mro__) # 輸出結果: (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
即新式類的繼承順序:F->D->B->E->C->A,廣度優先。
那什麼是繼承順序呢?就是尋找父類的順序,例如這裏調用 f1對象(F類的實例化對象)的 fun方法,若F類中沒有這個方法,就去D類中尋找,若D類中也沒有,就去B類中尋找,而後是E類,C類,A類,即按照 MRO列表上 從左到右查找基類~
若這裏的類是經典類,則繼承順序爲:F->D->B->A->E->C ,深度優先~
廣度優先 與 深度優先 的區別在於,廣度優先算法在查找基類的時候,若以後能找到的,則以後再進行查找,若以後找不到的,如今就去查找。例如,經過E,C也能再次找到A,則先不找A,可是經過E,C不能再次找到B,因此查找D以後就找B,查找B以後不會去查找A,而是在C以後再去查找A~
再看以下示例,因爲B和C只能經過一條途徑找到,因此新式類和經典類的繼承順序一致:
新式類繼承順序:F->D->B->E->C
經典類繼承順序:F->D->B->E->C
如果在鑽石繼承中用到了 super 關鍵字,super 會去調用父類的對應方法,可是 super 的本質並非直接找父類,而是根據調用者的節點位置的廣度優先順序來查找的。即 按照廣度優先的繼承順序找到上一個類~
Tip:super 關鍵字只有在新式類中有,因此確定是按照廣度優先的繼承順序來進行查找的~
class A: def func(self): print('A') class B(A): def func(self): super().func() print('B') class C(A): def func(self): super().func() print('C') class D(B, C): def func(self): super().func() print('D') d = D() d.func() # 輸出結果: A C B D
上述多個類的繼承關係以下圖所示:當前使用的是新式類,繼承順序爲:D->B->C->A(經典類的繼承順序爲:F->B->A->C) 其中super的調用過程以下:D類的 func() 方法中的 super().func() 會調用 B類的 func() 方法,B類的 func() 方法中的super().func() 會調用 C類的 func() 方法,C類的 func() 方法中的 super().func() 會調用 A類的 func() 方法~ 固然在單繼承中不會有這樣的問題~ .................^_^