Python 簡明教程 --- 21航徙,Python 繼承與多態(tài)

程序不是年輕的專利如贷,但是,它屬于年輕捉偏。

目錄

我們已經(jīng)知道封裝倒得,繼承多態(tài) 是面向?qū)ο蟮娜筇卣鳎嫦驅(qū)ο笳Z言都會提供這些機制夭禽。

1霞掺,封裝

這一節(jié)介紹類的私有屬性和方法的時候,我們已經(jīng)講到過封裝讹躯。

封裝就是在設(shè)計一個類的時候菩彬,只允許使用者訪問他需要的方法,將復雜的潮梯,沒有必要讓使用者知道的方法隱藏起來骗灶。這樣,使用者只需關(guān)注他需要的東西秉馏,為其屏蔽了復雜性耙旦。

私有性就是實現(xiàn)封裝的一種手段,這樣萝究,類的設(shè)計者就可以控制類中的哪些屬性和方法可以被使用者訪問到免都。一般,類中的屬性帆竹,和一些復雜的方法都不會暴露給使用者绕娘。

由于前邊的章節(jié)介紹過封裝,這里就不再舉例說明了栽连。

2险领,繼承

通過繼承的機制侨舆,可使得子類輕松的擁有父類中的屬性和方法繼承也是一種代碼復用的方式绢陌。

Python 支持類的繼承挨下,繼承的類叫做子類或者派生類被繼承的類叫做父類基類下面。

繼承的語法如下:

class 子類名(父類名):
    pass

子類名后邊的括號中复颈,寫入要繼承的父類。

object

在Python 的繼承體系中沥割,object 是最頂層類耗啦,它是所有類的父類。在定義一個類時机杜,如果沒有繼承任何類帜讲,會默認繼承object 類。如下兩種定義方式是等價的:

# 沒有顯示繼承任何類椒拗,默認繼承 object
class A1:
    pass

# 顯示繼承 object
class A2(object):
    pass

每個類中都有一個mro 方法似将,該方法可以打印類的繼承關(guān)系(順序)。我們來查看A1A2 的繼承關(guān)系:

>>> A1.mro()
[<class '__main__.A1'>, <class 'object'>]
>>>
>>> A2.mro()
[<class '__main__.A2'>, <class 'object'>]

可見這兩個類都繼承了 object 類蚀苛。

繼承中的__init__ 方法

當一個子類繼承一個父類時在验,如果子類中沒有定義__init__,在創(chuàng)建子類的對象時堵未,會調(diào)用父類的__init__ 方法腋舌,如下:

#! /usr/bin/env python3

class A(object):

    def __init__(self):
        print('A.__init__')

class B(A):
    pass

以上代碼中,B 繼承了A渗蟹,A 中有__init__ 方法块饺,B 中沒有__init__ 方法,創(chuàng)建類B 的對象b

>>> b = B()
A.__init__

可見A 中的__init__ 被執(zhí)行了雌芽。

方法覆蓋

如果類B 中也定義了__init__ 方法授艰,那么,就只會執(zhí)行B 中的__init__ 方法世落,而不會執(zhí)行A 中的__init__ 方法:

#! /usr/bin/env python3

class A(object):

    def __init__(self):
        print('A.__init__')

class B(A):

    def __init__(self):
        print('B.__init__')

此時創(chuàng)建B 的對象b

>>> b = B()
B.__init__

可見淮腾,此時只執(zhí)行了B 中的__init__ 方法。這其實是方法覆蓋的原因屉佳,因為子類中的__init__父類中的__init__ 的參數(shù)列表一樣来破,此時,子類中的方法覆蓋了父類中的方法忘古,所以創(chuàng)建對象b 時,只會執(zhí)行B 中的__init__ 方法诅诱。

當發(fā)生繼承關(guān)系(即一個子類繼承一個父類)時髓堪,如果子類中的一個方法與父類中的一個方法一模一樣(即方法名相同,參數(shù)列表也相同),這種情況就是方法覆蓋(子類中的方法會覆蓋父類中的方法)干旁。

方法重載

方法名參數(shù)列表都一樣時會發(fā)生方法覆蓋驶沼;當方法名一樣,參數(shù)列表不一樣時争群,會發(fā)生方法重載回怜。

在單個類中,代碼如下:

#! /usr/bin/env python3

class A(object):

    def __init__(self):
        print('A.__init__')

    def test(self):
        print('test...')

    def test(self, i):
        print('test... i:%s' % i)

A 中的兩個test 方法换薄,方法名相同玉雾,參數(shù)列表不同。

其實這種情況在JavaC++ 是允許的轻要,就是方法重載复旬。而在Python 中,雖然在類中這樣寫不會報錯冲泥,但實際上驹碍,下面的test(self, i) 已經(jīng)把上面的test(self) 給覆蓋掉了。創(chuàng)建出來的對象只能調(diào)用test(self, i)凡恍,而test(self) 是不存在的志秃。

示例:

>>> a = A()       # 創(chuàng)建 A 的對象 a
A.__init__
>>>
>>> a.test(123)   # 可以調(diào)用 test(self, i) 方法
test... i:123
>>>
>>> a.test()      # 調(diào)用 test(self) 發(fā)生異常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required positional argument: 'i'

在繼承關(guān)系中,代碼如下:

#! /usr/bin/env python3

class 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嚼酝,BA 中都有一個名為test 的方法浮还,但是參數(shù)列表不同。

這種情況跟在單個類中的情況是一樣的革半,在類B 中碑定,test(self, i) 會覆蓋A 中的test(self),類B 的對象只能調(diào)用test(self, i)又官,而不能調(diào)用test(self)延刘。

示例:

>>> b = B()        # 創(chuàng)建 B 的對象
B.__init__
>>> 
>>> b.test(123)    # 可以調(diào)用 test(self, i)  方法
test... i:123
>>>
>>> b.test()       # 調(diào)用 test(self) 方法,出現(xiàn)異常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required positional argument: 'i'

super() 方法

super() 方法用于調(diào)用父類中的方法六敬。

示例代碼:

#! /usr/bin/env python3

class 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__()     # 調(diào)用父類中的構(gòu)造方法

    def test(self, i):
        print('class_B test... i:%s' % i)
        super().test()         # 調(diào)用父類中的 test 方法

演示:

>>> b = B()          # 創(chuàng)建 B 的對象
B.__init__           # 調(diào)用 B 的構(gòu)造方法
A.__init__           # 調(diào)用 A 的構(gòu)造方法
>>>
>>> b.test(123)      # 調(diào)用 B 中的 test 方法 
class_B test... i:123
class_A test...      # 執(zhí)行 A 中的 test 方法 

is-a 關(guān)系

一個子類的對象碘赖,同時也是一個父類的對象,這叫做is-a 關(guān)系外构。但是一個父類的對象普泡,不一定是一個子類的對象。

這很好理解审编,就像撼班,貓一定是動物,但動物不一定是貓垒酬。

我們可以使用isinstance() 函數(shù)來判斷一個對象是否是一個類的實例砰嘁。

比如我們有如下兩個類件炉,Cat 繼承了 Animal

#! /usr/bin/env python3

class Animal(object):
    pass

class Cat(Animal):
    pass

來看下對象和類之間的從屬關(guān)系:

>>> a = Animal()           # 創(chuàng)建 Animal 的對象
>>> c = Cat()              # 創(chuàng)建 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++ 語言中允許多繼承磕蛇,但由于多繼承會使得類的繼承關(guān)系變得復雜。因此十办,到了Java 中秀撇,就禁止了多繼承的方式,取而代之的是橘洞,在Java 中允許同時繼承多個接口捌袜。

Python 中也允許多繼承,語法如下:

# 括號中可以寫多個父類
class 子類名(父類1, 父類2, ...):
    pass

我們構(gòu)造一個如下的繼承關(guān)系:

代碼如下:

#! /usr/bin/env python3

class 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()方法查看繼承關(guān)系:

>>> D.mro()
[<class 'Test.D'>, <class 'Test.B'>, <class 'Test.C'>, <class 'Test.A'>, <class 'object'>]

創(chuàng)建D 的對象:

>>> d = D()

如果類D 中有test() 方法霍衫,那么d.test() 肯定會調(diào)用D 中的test() 方法,這種情況很簡單侯养,不用多說敦跌。

當類D 中沒有test() 方法時,而它繼承的父類 BC 中都有 test() 方法逛揩,此時會調(diào)用哪個test() 呢柠傍?

>>> d.test()
class_B test...

可以看到d.test() 調(diào)用了類B 中的 test() 方法。

實際上這種情況下辩稽,Python 解釋器會根據(jù)D.mro() 的輸出結(jié)果來依次查找test() 方法惧笛,即查找順序是D->B->C->A->object

所以d.test() 調(diào)用了類B 中的 test() 方法逞泄。

建議:

由于多繼承會使類的繼承關(guān)系變得復雜患整,所以并不提倡過多的使用多繼承

4喷众,多態(tài)

多態(tài)從字面上理解就是一個事物可以呈現(xiàn)多種狀態(tài)各谚。繼承是多態(tài)的基礎(chǔ)。

在上面的例子中到千,類D 的對象d 調(diào)用test() 方法時昌渤,沿著繼承鏈D.mro())查找合適的test() 方法的過程,就是多態(tài)的表現(xiàn)過程憔四。

比如愈涩,我們有以下幾個類:

  • Animal:有一個speak() 方法
  • Cat:繼承Animal 類望抽,有自己的speak() 方法
  • Dog:繼承Animal 類,有自己的speak() 方法
  • Duck:繼承Animal 類履婉,有自己的speak() 方法

CatDog斟览,Duck 都屬于動物毁腿,因此都繼承Animal,代碼如下:

#! /usr/bin/env python3

class 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 函數(shù)苛茂,它接受一個參數(shù)animal已烤,在函數(shù)內(nèi),調(diào)用了speak() 方法妓羊。

實際上胯究,這種情況下,我們調(diào)用animal_speak 函數(shù)時躁绸,可以為它傳遞Animal 類型的對象裕循,以及任何的Animal 子類的對象。

傳遞Animal 的對象時净刮,調(diào)用了Animal 類中的 speak()

>>> animal_speak(Animal())
動物會說話...

傳遞Cat 的對象時剥哑,調(diào)用了Cat 類中的 speak()

>>> animal_speak(Cat())
喵喵...

傳遞Dog 的對象時,調(diào)用了Dog 類中的 speak()

>>> animal_speak(Dog())
汪汪...

傳遞Duck 的對象時淹父,調(diào)用了Duck 類中的 speak()

>>> animal_speak(Duck())
嘎嘎...

可以看到株婴,我們可以給animal_speak() 函數(shù)傳遞多種不同類型的對象,為animal_speak() 函數(shù)傳遞不同類型的參數(shù)暑认,輸出了不同的結(jié)果困介,這就是多態(tài)

5蘸际,鴨子類型

靜態(tài)類型語言中座哩,有嚴格的類型判斷,上面的animal_speak() 函數(shù)的參數(shù)只能傳遞Animal 及其子類的對象捡鱼。

而Python 屬于動態(tài)類型語言八回,不會進行嚴格的類型判斷。

因此驾诈,我們不僅可以為animal_speak() 函數(shù)傳遞Animal 及其子類的對象缠诅,還可以傳遞其它與Animal 類毫不相關(guān)的類的對象,只要該類中有speak() 方法就行乍迄。

這種特性管引,在Python 中被叫做鴨子類型,意思就是闯两,只要一個事物走起來像鴨子褥伴,叫起來像鴨子谅将,那么它就是鴨子,即使它不是真正的鴨子重慢。

從代碼上來說饥臂,只要一個類中有speak() 方法,那么就可以將該類的對象傳遞給animal_speak() 函數(shù)似踱。

比如隅熙,有一個鼓類Drum,其中有一個函數(shù)speak()

class Drum(object):
    def speak(self):
        print('咚咚...')

那么核芽,類Drum 的對象也可以傳遞給animal_speak() 函數(shù)囚戚,即使DrumAnimal 類毫不相關(guān):

>>> animal_speak(Drum())
咚咚...

從另一個角度來考慮,實際上Python 函數(shù)中的參數(shù)轧简,并沒有標明參數(shù)的類型驰坊。在animal_speak() 函數(shù)中,我們只是將參數(shù)叫做了animal 而已哮独,因此我們就認為animal_speak() 函數(shù)應該接受Animal 類及其子類的對象拳芙,其實這僅僅只是我們認為的而已。

計算機并不知道animal 的含義借嗽,如果我們將原來的animal_speak() 函數(shù):

def animal_speak(animal):
    animal.speak()

改寫成:

def animal_speak(a):
    a.speak()

實際上态鳖,我們知道,這兩個函數(shù)并沒有任何區(qū)別恶导。因此浆竭,參數(shù)a可以是任意的類型,只要a 中有speak() 方法就行惨寿。這就是Python 能夠表現(xiàn)出鴨子特性的原因邦泄。

(完。)


推薦閱讀:

Python 簡明教程 ---16裂垦,Python 高階函數(shù)
Python 簡明教程 ---17顺囊,Python 模塊與包
Python 簡明教程 ---18,Python 面向?qū)ο?/a>
Python 簡明教程 ---19蕉拢,Python 類與對象
Python 簡明教程 ---20害幅,Python 類中的屬性與方法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扩灯,一起剝皮案震驚了整個濱河市杆逗,隨后出現(xiàn)的幾起案子磅摹,更是在濱河造成了極大的恐慌,老刑警劉巖闸准,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件益愈,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機蒸其,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門敏释,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人摸袁,你說我怎么就攤上這事钥顽。” “怎么了但惶?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵耳鸯,是天一觀的道長。 經(jīng)常有香客問我膀曾,道長,這世上最難降的妖魔是什么阳啥? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任添谊,我火速辦了婚禮,結(jié)果婚禮上察迟,老公的妹妹穿的比我還像新娘斩狱。我一直安慰自己,他們只是感情好扎瓶,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布所踊。 她就那樣靜靜地躺著,像睡著了一般概荷。 火紅的嫁衣襯著肌膚如雪秕岛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天误证,我揣著相機與錄音继薛,去河邊找鬼。 笑死愈捅,一個胖子當著我的面吹牛遏考,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蓝谨,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼灌具,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了譬巫?” 一聲冷哼從身側(cè)響起咖楣,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缕题,沒想到半個月后截歉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡烟零,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年瘪松,在試婚紗的時候發(fā)現(xiàn)自己被綠了咸作。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡宵睦,死狀恐怖记罚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情壳嚎,我是刑警寧澤桐智,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站烟馅,受9級特大地震影響说庭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜郑趁,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一刊驴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寡润,春花似錦捆憎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至变抽,卻和暖如春础拨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瞬沦。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工太伊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逛钻。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓僚焦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親曙痘。 傳聞我的和親對象是個殘疾皇子芳悲,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容