面向對象之繼承與派生
閱讀目錄
- 一 初識繼承
- 二 繼承與抽象(先抽象再繼承)
- 三 繼承與重用性
- 四 派生
- 五 組合與重用性
- 六 接口與歸一化設計
- 七 抽象類
- 八 繼承實現的原理(可惡的菱形問題)
- 九 子類中調用父類的方法
一 初識繼承
什麽是繼承
繼承指的是類與類之間的關系,是一種什麽是什麽的關系,功能之一就是用來解決代碼重用問題(比如練習7中Garen與Riven類有很多冗余的代碼)
繼承是一種創建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可稱為基類或超類,新建的類稱為派生類或子類
python中類的繼承分為:單繼承和多繼承
class ParentClass1: #定義父類pass class ParentClass2: #定義父類 pass class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類 pass
查看繼承
>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看所有繼承的父類 (<class ‘__main__.ParentClass1‘>,) >>> SubClass2.__bases__ (<class ‘__main__.ParentClass1‘>, <class ‘__main__.ParentClass2‘>)
經典類與新式類
1.只有在python2中才分新式類和經典類,python3中統一都是新式類 2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類 3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類 3.在python3中,無論是否繼承object,都默認繼承object,即python3中所有類均為新式類 #關於新式類與經典類的區別,我們稍後討論
提示:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。
>>> ParentClass1.__bases__ (<class ‘object‘>,) >>> ParentClass2.__bases__ (<class ‘object‘>,)
二 繼承與抽象(先抽象再繼承)
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
1.將奧巴馬和梅西這倆對象比較像的部分抽取成類;
2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關註點,降低復雜度)
繼承:是基於抽象的結果,通過編程語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
三 繼承與重用性
使用繼承來重用代碼比較好的例子在開發程序的過程中,如果我們定義了一個類A,然後又想新建立另外一個類B,但是類B的大部分內容與類A的相同時
我們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的所有屬性(數據屬性和函數屬性),實現代碼重用
View Code提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟件中的一部分設置大部分,大大生了編程工作量,這就是常說的軟件重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定制新的數據類型,這樣就是大大縮短了軟件開發周期,對大型軟件開發來說,意義重大.
註意:像g1.life_value之類的屬性引用,會先從實例中找life_value然後去類中找,然後再去父類中找...直到最頂級的父類。
重點!!!:再看屬性查找
View Code
四 派生
當然子類也可以添加自己新的屬性或者在自己這裏重新定義這些屬性(不會影響到父類),需要註意的是,一旦重新定義了自己的屬性且與父類重名,那麽調用新增的屬性時,就以自己為準了。
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 比基尼 ‘‘‘
五 組合與重用性
軟件重用的重要方式除了繼承之外還有另外一種方式,即:組合
組合指的是,在一個類中以另外一個類的對象作為數據屬性,稱為類的組合
>>> class Equip: #武器裝備類 ... def fire(self): ... print(‘release Fire skill‘) ... >>> class Riven: #英雄Riven的類,一個英雄需要有裝備,因而需要組合Equip類 ... camp=‘Noxus‘ ... def __init__(self,nickname): ... self.nickname=nickname ... self.equip=Equip() #用Equip類產生一個裝備,賦值給實例的equip屬性 ... >>> r1=Riven(‘銳雯雯‘) >>> r1.equip.fire() #可以使用組合的類產生的對象所持有的方法 release Fire skill
組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場景皆不同,
1.繼承的方式
通過繼承建立了派生類與基類之間的關系,它是一種‘是‘的關系,比如白馬是馬,人是動物。
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如老師是人,學生是人
2.組合的方式
用組合的方式建立了類與組合的類之間的關系,它是一種‘有’的關系,比如教授有生日,教授教python和linux課程,教授有學生s1、s2、s3...
例子:繼承與組合當類之間有顯著不同,並且較小的類是較大的類所需要的組件時,用組合比較好
六 接口與歸一化設計
1.什麽是接口
java中的interfacePS:hi boy,給我開個查詢接口。。。此時的接口指的是:自己提供給使用者來調用自己功能的方式\方法\入口
2. 為何要用接口
接口提取了一群類共同的函數,可以把接口當做一個函數的集合。
然後讓子類去實現接口中的函數。
這麽做的意義在於歸一化,什麽叫歸一化,就是只要是基於同一個接口實現的類,那麽所有的這些類產生的對象在使用時,從用法上來說都一樣。
歸一化的好處在於:
1. 歸一化讓使用者無需關心對象的類是什麽,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
2. 歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合
2.1:就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然後做出針對性的設計:細致到什麽程度,視需求而定)。
2.2:再比如:我們有一個汽車接口,裏面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大眾汽車的類,他們都實現了汽車接口,這樣就好辦了,大家只需要學會了怎麽開汽車,那麽無論是本田,還是奧迪,還是大眾我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣
3. 模仿interface
在python中根本就沒有一個叫做interface的關鍵字,如果非要去模仿接口的概念
可以借助第三方模塊:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py裏使用zope.interface
文檔https://zopeinterface.readthedocs.io/en/latest/
設計模式:https://github.com/faif/python-patterns
也可以使用繼承:
繼承的兩種用途
一:繼承基類的方法,並且做出自己的改變或者擴展(代碼重用):實踐中,繼承的這種用途意義並不很大,甚至常常是有害的。因為它使得子類與基類出現強耦合。
二:聲明某個子類兼容於某基類,定義一個接口類(模仿java的Interface),接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能
View Code上面的代碼只是看起來像接口,其實並沒有起到接口的作用,子類完全可以不用去實現接口 ,這就用到了抽象類
七 抽象類
1 什麽是抽象類
與java一樣,python也有抽象類的概念但是同樣需要借助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化
2 為什麽要有抽象類
如果說類是從一堆對象中抽取相同的內容而來的,那麽抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麽是吃一個具體的香蕉,要麽是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。
從設計角度去看,如果類是從現實對象抽象而來的,那麽抽象類就是基於類抽象而來的。
從實現角度來看,抽象類與普通類的不同之處在於:抽象類中只能有抽象方法(沒有實現功能),該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與接口有點類似,但其實是不同的,即將揭曉答案
3. 在python中實現抽象類
View Code4. 抽象類與接口
抽象類的本質還是類,指的是一組類的相似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的相似性。
抽象類是一個介於類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現歸一化設計
八 繼承實現的原理(可惡的菱形問題)
1 繼承順序
View Code2 繼承原理(python如何實現的繼承)
python到底是如何實現繼承的,對於你定義的每一個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的所有基類的線性順序列表,例如
>>> F.mro() #等同於F.__mro__ [<class ‘__main__.F‘>, <class ‘__main__.D‘>, <class ‘__main__.B‘>, <class ‘__main__.E‘>, <class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘object‘>]
為了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構造是通過一個C3線性化算法來實現的。我們不去深究這個算法的數學原理,它實際上就是合並所有父類的MRO列表並遵循如下三條準則:
1.子類會先於父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類
九 子類中調用父類的方法
方法一:指名道姓,即父類名.父類方法()
#_*_coding:utf-8_*_ __author__ = ‘Linhaifeng‘ 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()View Code
方法二: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()View Code
強調:二者使用哪一種都可以,但最好不要混合使用
了解部分:
即使沒有直接繼承關系,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()) #[<class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘__main__.B‘>, <class ‘object‘>]View Code
指名道姓與super()的區別
#指名道姓 class A: def __init__(self): print(‘A的構造方法‘) class B(A): def __init__(self): print(‘B的構造方法‘) A.__init__(self) class C(A): def __init__(self): print(‘C的構造方法‘) A.__init__(self) class D(B,C): def __init__(self): print(‘D的構造方法‘) B.__init__(self) C.__init__(self) pass f1=D() #A.__init__被重復調用 ‘‘‘ D的構造方法 B的構造方法 A的構造方法 C的構造方法 A的構造方法 ‘‘‘ #使用super() class A: def __init__(self): print(‘A的構造方法‘) class B(A): def __init__(self): print(‘B的構造方法‘) super(B,self).__init__() class C(A): def __init__(self): print(‘C的構造方法‘) super(C,self).__init__() class D(B,C): def __init__(self): print(‘D的構造方法‘) super(D,self).__init__() f1=D() #super()會基於mro列表,往後找 ‘‘‘ D的構造方法 B的構造方法 C的構造方法 A的構造方法 ‘‘‘View Code
當你使用super()函數時,Python會在MRO列表上繼續搜索下一個類。只要每個重定義的方法統一使用super()並只調用它一次,那麽控制流最終會遍歷完整個MRO列表,每個方法也只會被調用一次(註意註意註意:使用super調用的所有屬性,都是從MRO列表當前的位置往後找,千萬不要通過看代碼去找繼承關系,一定要看MRO列表)
面向對象之繼承與派生