繼承
繼承指的是類與類之間的關係,是一種什麼“是”什麼的關係,繼承的功能之一就是用來解決代碼重用問題
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可以成爲基類或超類,新建的類稱爲派生類或子類
繼承可以分爲單繼承和多繼承
class Parentcalss1:
pass
class Parentcalss2:
pass
class Subcalss1(Parentcalss1): #單繼承,基類是ParentClass1,派生類是SubClass
pass
class Subcalss2(Parentcalss1,Parentcalss2):##python支持多繼承,用逗號分隔開多個繼承的類
pass
#查看繼承,可以用.__bases__進行查看繼承
print(Subcalss1.__bases__)
print(Subcalss2.__bases__)
#運行結果:
# (<class '__main__.Parentcalss1'>,)
# (<class '__main__.Parentcalss1'>, <class '__main__.Parentcalss2'>)
繼承與抽象
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
1.將奧巴馬和梅西這倆對象比較像的部分抽取成類;
2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關注點,降低複雜度)
繼承與重用性
在開發程序的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的內容相同。通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用
class Hero:
def __init__(self,nickname,livevalue,aggressvity):
self.nickname=nickname
self.livevalue=livevalue
self.aggressvity=aggressvity
def attack(self,enemy):
enemy.livevalue-=self.aggressvity
class Galen(Hero):
pass
class Riven(Hero):
pass
galen=Galen('Galun',1000,80)
riven=Riven('Riven',800,150)
galen.attack(riven)
print(riven.livevalue)
#運行結果:
#720
上述代碼定義了一個Hero(英雄)類,子類Galen和Riven分別繼承了父類所有的數據屬性和函數屬性,在對象galen(蓋倫)運行attack(攻擊)後,riven的livevalue(血量)就會減少。
屬性的查找
屬性的查找的順序是按照先子類,再父類的順序進行查找,一直查找到最頂級的父類
class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.f1()
class Bar(Foo):
def f1(self):
print('Bar.f1')
b=Bar()
b.f2()
# 打印結果:
# Foo.f2
# Bar.f1
注意上述程序,b.f2(),子類Bar中沒有f函數f2,於是回去查找父類Foo,父類中運行‘ print(‘Foo.f2’)’之後,程序運行self.f1(),相等於運行b.f1(),這時候程序依然先查找子類屬性,再查找父類屬性,因此打印結果是‘Bar.f1’
派生
當然子類也可以添加自己新的屬性或者在自己這裏重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那麼調用新增的屬性時,就以自己爲準了。
class ParentA:
name='A'
def print_info(self):
print('來自父類的print_info')
class SonA(ParentA):
def print_info(self):
print('來自子類的print_info')
k=SonA()
k.print_info()
#運行結果:
#來自子類的print_info
在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能需要重用父類中重名的那個函數功能,注意參數要對應好,以及新傳入的參數
class Hero:
def __init__(self,nickname,livevalue,aggressvity):
self.nickname=nickname
self.livevalue=livevalue
self.aggressvity=aggressvity
def attack(self,enemy):
enemy.livevalue-=self.aggressvity
class Galen(Hero):
pass
class Riven(Hero):
def __init__(self,nickname,livevalue,aggressvity,skin):
Hero.__init__(self,nickname,livevalue,aggressvity)
self.skin=skin #子類Riven增加自己的新屬性
riven=Riven('ruiwen',800,150,'冠軍之刃')
print(riven.__dict__)
#運行結果
#{'nickname': 'ruiwen', 'livevalue': 800, 'aggressvity': 150, 'skin': '冠軍之刃'}
上面子類派生出新的方法,上述‘Hero.init(self,nickname,livevalue,aggressvity)’叫做指名道姓的方法,即父類名.父類方法()
還有一種方法,採用super(),也可以實現相同的功能
class Hero:
def __init__(self,nickname,livevalue,aggressvity):
self.nickname=nickname
self.livevalue=livevalue
self.aggressvity=aggressvity
def attack(self,enemy):
enemy.livevalue-=self.aggressvity
class Galen(Hero):
pass
class Riven(Hero):
def __init__(self,nickname,livevalue,aggressvity,skin):
#Hero.__init__(self,nickname,livevalue,aggressvity)
super().__init__(nickname,livevalue,aggressvity)
#super()相當於super(self,Hero),可以簡寫
self.skin=skin #子類Riven增加自己的新屬性
riven=Riven('ruiwen',800,150,'冠軍之刃')
print(riven.__dict__)
#運行結果
#{'nickname': 'ruiwen', 'livevalue': 800, 'aggressvity': 150, 'skin': '冠軍之刃'}
注意:super()後面只需要傳入父類原來的幾個屬性,不需要傳入self,傳入參數多寫self,會報錯
繼承的實現原理
python到底是如何實現繼承的,對於你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如
class Parentcalss1:
pass
class Parentcalss2:
pass
class Subcalss1(Parentcalss1): #單繼承,基類是ParentClass1,派生類是SubClass
pass
class Subcalss2(Parentcalss1,Parentcalss2):##python支持多繼承,用逗號分隔開多個繼承的類
pass
print(Subcalss1.mro())
#[<class '__main__.Subcalss1'>,
# <class '__main__.Parentcalss1'>,
# <class 'object'>]
print(Subcalss2.mro())
#[<class '__main__.Subcalss2'>,
# <class '__main__.Parentcalss1'>,
# <class '__main__.Parentcalss2'>,
# <class 'object'>]
爲了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類爲止。而這個MRO列表的構造是通過一個C3線性化算法來實現的。實際上就是合併所有父類的MRO列表並遵循如下三條準則:
1.子類會先於父類被檢查
2.多個父類會根據它們在列表中的順序被檢查 (上述代碼中先Parentcalss1,再Parentcalss2)
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類
python區別於Java和C:在Java和C#中子類只能繼承一個父類