python多态_Python 简明教程 21,Python 继承与多态
程序不是年輕的專利,但是,它屬于年輕。
目錄
目錄我們已經知道封裝,繼承和多態 是面向對象的三大特征,面向對象語言都會提供這些機制。
1,封裝
在這一節介紹類的私有屬性和方法的時候,我們已經講到過封裝。
封裝就是在設計一個類的時候,只允許使用者訪問他需要的方法,將復雜的,沒有必要讓使用者知道的方法隱藏起來。這樣,使用者只需關注他需要的東西,為其屏蔽了復雜性。
私有性就是實現封裝的一種手段,這樣,類的設計者就可以控制類中的哪些屬性和方法可以被使用者訪問到。一般,類中的屬性,和一些復雜的方法都不會暴露給使用者。
由于前邊的章節介紹過封裝,這里就不再舉例說明了。
2,繼承
通過繼承的機制,可使得子類輕松的擁有父類中的屬性和方法。繼承也是一種代碼復用的方式。
Python 支持類的繼承,繼承的類叫做子類或者派生類,被繼承的類叫做父類或基類。
繼承的語法如下:
class?子類名(父類名):????pass
在子類名后邊的括號中,寫入要繼承的父類。
object 類
在Python 的繼承體系中,object 是最頂層類,它是所有類的父類。在定義一個類時,如果沒有繼承任何類,會默認繼承object 類。如下兩種定義方式是等價的:
#?沒有顯示繼承任何類,默認繼承?objectclass?A1:
????pass
#?顯示繼承?object
class?A2(object):
????pass
每個類中都有一個mro 方法,該方法可以打印類的繼承關系(順序)。我們來查看A1 和 A2 的繼承關系:
>>>?A1.mro()[,?]>>>>>>?A2.mro()
[,?]
可見這兩個類都繼承了 object 類。
繼承中的__init__ 方法
當一個子類繼承一個父類時,如果子類中沒有定義__init__,在創建子類的對象時,會調用父類的__init__ 方法,如下:
#!?/usr/bin/env?python3class?A(object):
????def?__init__(self):
????????print('A.__init__')
class?B(A):
????pass
以上代碼中,B 繼承了A,A 中有__init__ 方法,B 中沒有__init__ 方法,創建類B 的對象b:
>>>?b?=?B()A.__init__
可見A 中的__init__ 被執行了。
方法覆蓋
如果類B 中也定義了__init__ 方法,那么,就只會執行B 中的__init__ 方法,而不會執行A 中的__init__ 方法:
#!?/usr/bin/env?python3class?A(object):
????def?__init__(self):
????????print('A.__init__')
class?B(A):
????def?__init__(self):
????????print('B.__init__')
此時創建B 的對象b:
>>>?b?=?B()B.__init__
可見,此時只執行了B 中的__init__ 方法。這其實是方法覆蓋的原因,因為子類中的__init__ 與父類中的__init__ 的參數列表一樣,此時,子類中的方法覆蓋了父類中的方法,所以創建對象b 時,只會執行B 中的__init__ 方法。
當發生繼承關系(即一個子類繼承一個父類)時,如果子類中的一個方法與父類中的一個方法一模一樣(即方法名相同,參數列表也相同),這種情況就是方法覆蓋(子類中的方法會覆蓋父類中的方法)。
方法重載
當方法名與參數列表都一樣時會發生方法覆蓋;當方法名一樣,參數列表不一樣時,會發生方法重載。
在單個類中,代碼如下:
#!?/usr/bin/env?python3class?A(object):
????def?__init__(self):
????????print('A.__init__')
????def?test(self):
????????print('test...')
????def?test(self,?i):
????????print('test...?i:%s'?%?i)
類A 中的兩個test 方法,方法名相同,參數列表不同。
其實這種情況在Java 和 C++ 是允許的,就是方法重載。而在Python 中,雖然在類中這樣寫不會報錯,但實際上,下面的test(self, i) 已經把上面的test(self) 給覆蓋掉了。創建出來的對象只能調用test(self, i),而test(self) 是不存在的。
示例:
>>>?a?=?A()???????#?創建?A?的對象?aA.__init__
>>>
>>>?a.test(123)???#?可以調用?test(self,?i)?方法
test...?i:123
>>>
>>>?a.test()??????#?調用?test(self)?發生異常
Traceback?(most?recent?call?last):
??File?"",?line?1,?in?
TypeError:?test()?missing?1?required?positional?argument:?'i'
在繼承關系中,代碼如下:
#!?/usr/bin/env?python3class?A(object):
????def?__init__(self):
????????print('A.__init__')
????def?test(self):
????????print('test...')
class?B(A):
????def?__init__(self):
????????print('B.__init__')
????def?test(self,?i):
????????print('test...?i:%s'?%?i)
上面代碼中B 繼承了A,B 和 A 中都有一個名為test 的方法,但是參數列表不同。
這種情況跟在單個類中的情況是一樣的,在類B 中,test(self, i) 會覆蓋A 中的test(self),類B 的對象只能調用test(self, i),而不能調用test(self)。
示例:
>>>?b?=?B()????????#?創建?B?的對象B.__init__
>>>?
>>>?b.test(123)????#?可以調用?test(self,?i)??方法
test...?i:123
>>>
>>>?b.test()???????#?調用?test(self)?方法,出現異常
Traceback?(most?recent?call?last):
??File?"",?line?1,?in?
TypeError:?test()?missing?1?required?positional?argument:?'i'
super() 方法
super() 方法用于調用父類中的方法。
示例代碼:
#!?/usr/bin/env?python3class?A(object):
????def?__init__(self):
????????print('A.__init__')
????def?test(self):
????????print('class_A?test...')
class?B(A):
????def?__init__(self):
????????print('B.__init__')
????????super().__init__()?????#?調用父類中的構造方法
????def?test(self,?i):
????????print('class_B?test...?i:%s'?%?i)
????????super().test()?????????#?調用父類中的?test?方法
演示:
>>>?b?=?B()??????????#?創建?B?的對象B.__init__???????????#?調用?B?的構造方法
A.__init__???????????#?調用?A?的構造方法
>>>
>>>?b.test(123)??????#?調用?B?中的?test?方法?
class_B?test...?i:123
class_A?test...??????#?執行?A?中的?test?方法?
is-a 關系
一個子類的對象,同時也是一個父類的對象,這叫做is-a 關系。但是一個父類的對象,不一定是一個子類的對象。
這很好理解,就像,貓一定是動物,但動物不一定是貓。
我們可以使用isinstance() 函數來判斷一個對象是否是一個類的實例。
比如我們有如下兩個類,Cat 繼承了 Animal:
#!?/usr/bin/env?python3class?Animal(object):
????pass
class?Cat(Animal):
????pass
來看下對象和類之間的從屬關系:
>>>?a?=?Animal()???????????#?創建?Animal?的對象>>>?c?=?Cat()??????????????#?創建?Cat?的對象
>>>?
>>>?isinstance(a,?Animal)??#?a?一定是?Animal?的實例
True
>>>?isinstance(c,?Cat)?????#?c?一定是?Cat?的實例
True
>>>?
>>>?isinstance(c,?Animal)??#?Cat?繼承了?Animal,所以?c?也是?Animal?的實例
True
>>>?isinstance(a,?Cat)?????#?但?a?不是?Cat?的實例
False
3,多繼承
多繼承就是一個子類同時繼承多個父類,這樣,這個子類就同時擁有了多個父類的特性。
C++ 語言中允許多繼承,但由于多繼承會使得類的繼承關系變得復雜。因此,到了Java 中,就禁止了多繼承的方式,取而代之的是,在Java 中允許同時繼承多個接口。
Python 中也允許多繼承,語法如下:
#?括號中可以寫多個父類class?子類名(父類1,?父類2,?...):
????pass
我們構造一個如下的繼承關系:
Python 多繼承代碼如下:
#!?/usr/bin/env?python3class?A(object):
????def?test(self):
????????print('class_A?test...')
class?B(A):
????def?test(self):
????????print('class_B?test...')?
class?C(A):
????def?test(self):
????????print('class_C?test...')?
class?D(B,?C):
????pass
類A,B,C 中都有test() 方法,D 中沒有test() 方法。
使用D 類中的mro()方法查看繼承關系:
>>>?D.mro()[,?,?,?,?]
創建D 的對象:
>>>?d?=?D()如果類D 中有test() 方法,那么d.test() 肯定會調用D 中的test() 方法,這種情況很簡單,不用多說。
當類D 中沒有test() 方法時,而它繼承的父類 B 和 C 中都有 test() 方法,此時會調用哪個test() 呢?
>>>?d.test()class_B?test...
可以看到d.test() 調用了類B 中的 test() 方法。
實際上這種情況下,Python 解釋器會根據D.mro() 的輸出結果來依次查找test() 方法,即查找順序是D->B->C->A->object。
所以d.test() 調用了類B 中的 test() 方法。
建議:
由于多繼承會使類的繼承關系變得復雜,所以并不提倡過多的使用多繼承。
4,多態
多態從字面上理解就是一個事物可以呈現多種狀態。繼承是多態的基礎。
在上面的例子中,類D 的對象d 調用test() 方法時,沿著繼承鏈(D.mro())查找合適的test() 方法的過程,就是多態的表現過程。
比如,我們有以下幾個類:
- Animal:有一個speak() 方法
- Cat:繼承Animal 類,有自己的speak() 方法
- Dog:繼承Animal 類,有自己的speak() 方法
- Duck:繼承Animal 類,有自己的speak() 方法
Cat,Dog,Duck 都屬于動物,因此都繼承Animal,代碼如下:
#!?/usr/bin/env?python3class?Animal(object):
????def?speak(self):
????????print('動物會說話...')
class?Cat(Animal):
????def?speak(self):
????????print('喵喵...')?
class?Dog(Animal):
????def?speak(self):
????????print('汪汪...')?
class?Duck(Animal):
????def?speak(self):
????????print('嘎嘎...')?
def?animal_speak(animal):
????animal.speak()
我們還定義了一個animal_speak 函數,它接受一個參數animal,在函數內,調用了speak() 方法。
實際上,這種情況下,我們調用animal_speak 函數時,可以為它傳遞Animal 類型的對象,以及任何的Animal 子類的對象。
傳遞Animal 的對象時,調用了Animal 類中的 speak():
>>>?animal_speak(Animal())動物會說話...
傳遞Cat 的對象時,調用了Cat 類中的 speak():
>>>?animal_speak(Cat())喵喵...
傳遞Dog 的對象時,調用了Dog 類中的 speak():
>>>?animal_speak(Dog())汪汪...
傳遞Duck 的對象時,調用了Duck 類中的 speak():
>>>?animal_speak(Duck())嘎嘎...
可以看到,我們可以給animal_speak() 函數傳遞多種不同類型的對象,為animal_speak() 函數傳遞不同類型的參數,輸出了不同的結果,這就是多態。
5,鴨子類型
在靜態類型語言中,有嚴格的類型判斷,上面的animal_speak() 函數的參數只能傳遞Animal 及其子類的對象。
而Python 屬于動態類型語言,不會進行嚴格的類型判斷。
因此,我們不僅可以為animal_speak() 函數傳遞Animal 及其子類的對象,還可以傳遞其它與Animal 類毫不相關的類的對象,只要該類中有speak() 方法就行。
這種特性,在Python 中被叫做鴨子類型,意思就是,只要一個事物走起來像鴨子,叫起來像鴨子,那么它就是鴨子,即使它不是真正的鴨子。
從代碼上來說,只要一個類中有speak() 方法,那么就可以將該類的對象傳遞給animal_speak() 函數。
比如,有一個鼓類Drum,其中有一個函數speak():
class?Drum(object):????def?speak(self):
????????print('咚咚...')
那么,類Drum 的對象也可以傳遞給animal_speak() 函數,即使Drum 與Animal 類毫不相關:
>>>?animal_speak(Drum())咚咚...
從另一個角度來考慮,實際上Python 函數中的參數,并沒有標明參數的類型。在animal_speak() 函數中,我們只是將參數叫做了animal 而已,因此我們就認為animal_speak() 函數應該接受Animal 類及其子類的對象,其實這僅僅只是我們認為的而已。
計算機并不知道animal 的含義,如果我們將原來的animal_speak() 函數:
def?animal_speak(animal):????animal.speak()
改寫成:
def?animal_speak(a):????a.speak()
實際上,我們知道,這兩個函數并沒有任何區別。因此,參數a可以是任意的類型,只要a 中有speak() 方法就行。這就是Python 能夠表現出鴨子特性的原因。
(完。)
推薦閱讀:
Python 簡明教程 --- 16,Python 高階函數
Python 簡明教程 --- 17,Python 模塊與包
Python 簡明教程 --- 18,Python 面向對象
Python 簡明教程 --- 19,Python 類與對象
Python 簡明教程 --- 20,Python 類中的屬性與方法
歡迎關注作者公眾號,獲取更多技術干貨。
↙點擊“閱讀原文”查看更多精彩內容
總結
以上是生活随笔為你收集整理的python多态_Python 简明教程 21,Python 继承与多态的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 半大的小鸡能吃生大米吗?
- 下一篇: tensorflow lstm 预测_图