Python面向對象編程——繼承與派生
一、初始繼承
1、什么是繼承
繼承指的是類與類之間的關系,是一種
什么“是”什么
的關系,繼承的功能之一就是用來解決代碼重用問題。
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可以成為基類或超類,新建的類稱為派生類或子類
2、python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類
pass
class ParentClass2: #定義父類
pass
class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
pass
3、查看繼承
>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看所有繼承的父類
(
,)
>>> SubClass2.__bases__
(
,
)
4、經典類與新式類
1.只有在python2中才分新式類和經典類,python3中統一都是新式類
2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類
3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類
4.在python3中,無論是否繼承object,都默認繼承object,即python3中所有類均為新式類
注:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類,它提供了一些常見方法(如
__str__
)的實現。
>>> ParentClass1.__bases__
(
,)
>>> ParentClass2.__bases__
(
,)
二、繼承與抽象(先抽象再繼承)
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
1、將奧巴馬和梅西這倆對象比較像的部分抽取成類;
2、將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關注點,降低復雜度)
繼承:是基于抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類。
三、繼承與重用性
在開發程序的過程中,如果我們定義了一個類A,然后又想新建立另外一個類B,但是類B的大部分內容與類A的相同時,我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用。
class Hero:
def __init__(self,nickname,aggressivity,life_value):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
def move_forward(self):
print('%s move forward' %self.nickname)
def move_backward(self):
print('%s move backward' %self.nickname)
def move_left(self):
print('%s move forward' %self.nickname)
def move_right(self):
print('%s move forward' %self.nickname)
def attack(self,enemy):
enemy.life_value-=self.aggressivity
class Garen(Hero):
pass
class Riven(Hero):
pass
g1=Garen('草叢倫',100,300)
r1=Riven('銳雯雯',57,200)
print(g1.life_value) #結果:300
r1.attack(g1)
print(g1.life_value) #結果:243
注:用已經有的類建立一個新的類,這樣就重用了已經有的軟件中的一部分設置大部分,大大節省了編程工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定制新的數據類型,這樣就是大大縮短了軟件開發周期,對大型軟件開發來說,意義重大。
四、再看屬性查找
像g1.life_value之類的屬性引用,會先從實例中找life_value然后去類中找,然后再去父類中找...直到最頂級的父類。那么如何解釋下面的打印結果呢?
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
五、派生
當然子類也可以添加自己新的屬性或者在自己這里重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那么調用新增的屬性時,就以自己為準了。
class Riven(Hero):
camp='Noxus'
def attack(self,enemy): #在自己這里定義新的attack,不再使用父類的attack,且不會影響父類
print('from riven')
def fly(self): #在自己這里定義新的
print('%s is flying' %self.nickname)
在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能需要重用父類中重名的那個函數功能,應該是用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,因此即便是self參數也要為其傳值。
class Riven(Hero):
camp='Noxus'
def __init__(self,nickname,aggressivity,life_value,skin):
Hero.__init__(self,nickname,aggressivity,life_value) #調用父類功能
self.skin=skin #新屬性
def attack(self,enemy): #在自己這里定義新的attack,不再使用父類的attack,且不會影響父類
Hero.attack(self,enemy) #調用功能
print('from riven')
def fly(self): #在自己這里定義新的
print('%s is flying' %self.nickname)
r1=Riven('銳雯雯',57,200,'比基尼')
r1.fly()
print(r1.skin)
'''
運行結果
銳雯雯 is flying
比基尼
'''
六、繼承的實現與原理
python到底是如何實現繼承的,對于你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如
>>> F.mro() #等同于F.__mro__
[
,
,
,
,
,
,
]
為了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。而這個MRO列表的構造是通過一個C3線性化算法來實現的。我們不去深究這個算法的數學原理,它實際上就是合并所有父類的MRO列表并遵循如下三條準則:
1、子類會先于父類被檢查。
2、多個父類會根據它們在列表中的順序被檢查。
3、如果對下一個類存在兩個合法的選擇,選擇第一個父類。
在Java和C#中子類只能繼承一個父類,而Python中子類可以同時繼承多個父類,如果繼承了多個父類,那么屬性的查找方式有兩種,分別是:深度優先和廣度優先。
示范代碼:
class A(object):
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
# def test(self):
# print('from F')
pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有這個屬性可以查看線性列表,經典類沒有這個屬性
#新式類繼承順序:F->D->B->E->C->A
#經典類繼承順序:F->D->B->A->E->C
#python3中統一都是新式類
#pyhon2中才分新式類與經典類
七、在子類中調用父類的方法
在子類派生出的新方法中,往往需要重用父類的方法,我們有兩種方式實現
方式一:指名道姓,即父類名.父類方法()
class Vehicle: #定義交通工具類
Country='China'
def __init__(self,name,speed,load,power):
self.name=name
self.speed=speed
self.load=load
self.power=power
def run(self):
print('開動啦...')
class Subway(Vehicle): #地鐵
def __init__(self,name,speed,load,power,line):
Vehicle.__init__(self,name,speed,load,power)
self.line=line
def run(self):
print('地鐵%s號線歡迎您' %self.line)
Vehicle.run(self) # 指名道姓的調用
line13=Subway('中國地鐵','180m/s','1000人/箱','電',13)
line13.run()
方式二:super()
class Vehicle: #定義交通工具類
Country='China'
def __init__(self,name,speed,load,power):
self.name=name
self.speed=speed
self.load=load
self.power=power
def run(self):
print('開動啦...')
class Subway(Vehicle): #地鐵
def __init__(self,name,speed,load,power,line):
#super(Subway,self) 就相當于實例本身 在python3中super()等同于super(Subway,self)
super().__init__(name,speed,load,power)
self.line=line
def run(self):
print('地鐵%s號線歡迎您' %self.line)
super(Subway,self).run()
class Mobike(Vehicle):#摩拜單車
pass
line13=Subway('中國地鐵','180m/s','1000人/箱','電',13)
line13.run()
這兩種方式的區別是:方式一是跟繼承沒有關系的,而方式二的super()是依賴于繼承的, 并且即使沒有直接繼承關系,super仍然會按照mro繼續往后查找。
#A沒有繼承B,但是A內super會基于C.mro()繼續往后找
class A:
def test(self):
super().test()
class B:
def test(self):
print('from B')
class C(A,B):
pass
c=C()
c.test() #打印結果:from B
print(C.mro())
#[
,
,
,
]
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
