類(4)

2018-02-24 15:48 更新

本節(jié)介紹類中一個(gè)非常重要的東西——繼承,其實(shí)也沒有那么重要,只是聽起來似乎有點(diǎn)讓初學(xué)者暈頭轉(zhuǎn)向,然后就感覺它屬于很高級(jí)的東西,真是情況如何?學(xué)了之后你自然有感受。

在現(xiàn)實(shí)生活中,“繼承”意味著一個(gè)人從另外一個(gè)人那里得到了一些什么,比如“繼承革命先烈的光榮傳統(tǒng)”、“某人繼承他老爹的萬貫家產(chǎn)”等。總之,“繼承”之后,自己就在所繼承的方面省力氣、不用勞神費(fèi)心,能輕松得到,比如繼承了萬貫家產(chǎn),自己就一夜之間變成富豪。如果繼承了“革命先烈的光榮傳統(tǒng)”,自己是不是一下就變成革命者呢?

當(dāng)然,生活中的繼承或許不那么嚴(yán)格,但是編程語言中的繼承是有明確規(guī)定和穩(wěn)定的預(yù)期結(jié)果的。

繼承(Inheritance)是面向?qū)ο筌?件技術(shù)當(dāng)中的一個(gè)概念。如果一個(gè)類別A“繼承自”另一個(gè)類別B,就把這個(gè)A稱為“B的子類別”,而把B稱為“A的父類別”,也可以稱“B是A的超類”。

繼承可以使得子類別具有父類別的各種屬性和方法,而不需要再次編寫相同的代碼。在令子類別繼承父類別的同時(shí),可以重新定義某些屬性,并重寫某些方法,即覆蓋父類別的原有屬性和方法,使其獲得與父類別不同的功能。另外,為子類別追加新的屬性和方法也是常見的做法。 (源自維基百科)

由上面對(duì)繼承的表述,可以簡(jiǎn)單總結(jié)出繼承的意圖或者好處:

  • 可以實(shí)現(xiàn)代碼重用,但不是僅僅實(shí)現(xiàn)代碼重用,有時(shí)候根本就沒有重用
  • 實(shí)現(xiàn)屬性和方法繼承

誠(chéng)然,以上也不是全部,隨著后續(xù)學(xué)習(xí),對(duì)繼承的認(rèn)識(shí)會(huì)更深刻。好友令狐蟲曾經(jīng)這樣總結(jié)繼承:

從技術(shù)上說,OOP里,繼承最主要的用途是實(shí)現(xiàn)多態(tài)。對(duì)于多態(tài)而言,重要的是接口繼承性,屬性和行為是否存在繼承性,這是不一定的。事實(shí)上,大量工程實(shí)踐表明,重度的行為繼承會(huì)導(dǎo)致系統(tǒng)過度復(fù)雜和臃腫,反而會(huì)降低靈活性。因此現(xiàn)在比較提倡的是基于接口的輕度繼承理念。這種模型里因?yàn)楦割悾ń涌陬悾┩耆珱]有代碼,因此根本談不上什么代碼復(fù)用了。

在Python里,因?yàn)榇嬖贒uck Type,接口定義的重要性大大的降低,繼承的作用也進(jìn)一步的被削弱了。

另外,從邏輯上說,繼承的目的也不是為了復(fù)用代碼,而是為了理順關(guān)系。

他是大牛,或許讀者感覺比較高深,沒關(guān)系,隨著你的實(shí)踐經(jīng)驗(yàn)的積累,你也能對(duì)這個(gè)問題有自己獨(dú)到的見解。

或許你也要問我的觀點(diǎn)是什么?我的觀點(diǎn)就是:走著瞧!怎么理解?繼續(xù)向下看,只有你先深入這個(gè)問題,才能跳到更高層看這個(gè)問題。小馬過河的故事還記得吧?只有親自走入河水中,才知道河水的深淺。

對(duì)于python中的繼承,前面一直在使用,那就是我們寫的類都是新式類,所有新式類都是繼承自object類。不要忘記,新式類的一種寫法:

class NewStyle(object):
    pass

這就是典型的繼承。

基本概念

#!/usr/bin/env python
# coding=utf-8

__metaclass__ = type

class Person:
    def speak(self):
        print "I love you."

    def setHeight(self):
        print "The height is: 1.60m ."

    def breast(self, n):
        print "My breast is: ",n

class Girl(Person):
    def setHeight(self):
        print "The height is:1.70m ."

if __name__ == "__main__":
    cang = Girl()
    cang.setHeight()
    cang.speak()
    cang.breast(90)

上面這個(gè)程序,保存之后運(yùn)行:

$ python 20901.py 
The height is:1.70m .
I love you.
My breast is:  90

對(duì)以上程序進(jìn)行解釋,從中體會(huì)繼承的概念和方法。

首先定義了一個(gè)類Person,在這個(gè)類中定義了三個(gè)方法。注意,沒有定義初始化函數(shù),初始化函數(shù)在類中不是必不可少的。

然后又定義了一個(gè)類Girl,這個(gè)類的名字后面的括號(hào)中,是上一個(gè)類的名字,這就意味著Girl繼承了Person,Girl是Person的子類,Person是Girl的父類。

既然是繼承了Person,那么Girl就全部擁有了Person中的方法和屬性(上面的例子雖然沒有列出屬性)。但是,如果Girl里面有一個(gè)和Person同樣名稱的方法,那么就把Person中的同一個(gè)方法遮蓋住了,顯示的是Girl中的方法,這叫做方法的重寫。

實(shí)例化類Girl之后,執(zhí)行實(shí)例方法cang.setHeight(),由于在類Girl中重寫了setHeight方法,那么Person中的那個(gè)方法就不顯作用了,在這個(gè)實(shí)例方法中執(zhí)行的是類Girl中的方法。

雖然在類Girl中沒有看到speak方法,但是因?yàn)樗^承了Person,所以cang.speak()就執(zhí)行類Person中的方法。同理cang.breast(90),它們就好像是在類Girl里面已經(jīng)寫了這兩個(gè)方法一樣。既然繼承了,就是我的了。

多重繼承

所謂多重繼承,就是只某一個(gè)類的父類,不止一個(gè),而是多個(gè)。比如:

#!/usr/bin/env python
# coding=utf-8

__metaclass__ = type

class Person:
    def eye(self):
        print "two eyes"

    def breast(self, n):
        print "The breast is: ",n

class Girl:
    age = 28
    def color(self):
        print "The girl is white"

class HotGirl(Person, Girl):
    pass

if __name__ == "__main__":
    kong = HotGirl()
    kong.eye()
    kong.breast(90)
    kong.color()
    print kong.age

在這個(gè)程序中,前面有兩個(gè)類:Person和Girl,然后第三個(gè)類HotGirl繼承了這兩個(gè)類,注意觀察繼承方法,就是在類的名字后面的括號(hào)中把所繼承的兩個(gè)類的名字寫上。但是第三個(gè)類中什么方法也沒有。

然后實(shí)例化類HotGirl,既然繼承了上面的兩個(gè)類,那么那兩個(gè)類的方法就都能夠拿過來使用。保存程序,運(yùn)行一下看看

$ python 20902.py 
two eyes
The breast is:  90
The girl is white
28

值得注意的是,這次在類Girl中,有一個(gè)age = 28,在對(duì)HotGirl實(shí)例化之后,因?yàn)槔^承的原因,這個(gè)類屬性也被繼承到HotGirl中,因此通過實(shí)例屬性kong.age一樣能夠得到該數(shù)據(jù)。

由上述兩個(gè)實(shí)例,已經(jīng)清楚看到了繼承的特點(diǎn),即將父類的方法和屬性全部承接到子類中;如果子類重寫了父類的方法,就使用子類的該方法,父類的被遮蓋。

多重繼承的順序

多重繼承的順序很必要了解。比如,如果一個(gè)子類繼承了兩個(gè)父類,并且兩個(gè)父類有同樣的方法或者屬性,那么在實(shí)例化子類后,調(diào)用那個(gè)方法或?qū)傩?,是屬于哪個(gè)父類的呢?造一個(gè)沒有實(shí)際意義,純粹為了解決這個(gè)問題的程序:

#!/usr/bin/env python
# coding=utf-8

class K1(object):
    def foo(self):
        print "K1-foo"

class K2(object):
    def foo(self):
        print "K2-foo"
    def bar(self):
        print "K2-bar"

class J1(K1, K2):
    pass

class J2(K1, K2):
    def bar(self):
        print "J2-bar"

class C(J1, J2):
    pass

if __name__ == "__main__":
    print C.__mro__
    m = C()
    m.foo()
    m.bar()

這段代碼,保存后運(yùn)行:

$ python 20904.py 
(<class '__main__.C'>, <class '__main__.J1'>, <class '__main__.J2'>, <class '__main__.K1'>, <class '__main__.K2'>, <type 'object'>)
K1-foo
J2-bar

代碼中的print C.__mro__是要打印出類的繼承順序。從上面清晰看出來了。如果要執(zhí)行foo()方法,首先看J1,沒有,看J2,還沒有,看J1里面的K1,有了,即C==>J1==>J2==>K1;bar()也是按照這個(gè)順序,在J2中就找到了一個(gè)。

這種對(duì)繼承屬性和方法搜索的順序稱之為“廣度優(yōu)先”。

新式類用以及python3.x中都是按照此順序原則搜尋屬性和方法的。

但是,在舊式類中,是按照“深度優(yōu)先”的順序的。因?yàn)楹竺孀x者也基本不用舊式類,所以不舉例。如果讀者愿意,可以自己模仿上面代碼,探索舊式類的“深度優(yōu)先”含義。

super函數(shù)

對(duì)于初始化函數(shù)的繼承,跟一般方法的繼承,還有點(diǎn)不同??梢钥聪旅娴睦樱?/p>

#!/usr/bin/env python
# coding=utf-8

__metaclass__ = type

class Person:
    def __init__(self):
        self.height = 160

    def about(self, name):
        print "{} is about {}".format(name, self.height)

class Girl(Person):
    def __init__(self):
        self.breast = 90

    def about(self, name):
        print "{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast)

if __name__ == "__main__":
    cang = Girl()
    cang.about("canglaoshi")

在上面這段程序中,類Girl繼承了類Person。在類Girl中,初始化設(shè)置了self.breast = 90,由于繼承了Person,按照前面的經(jīng)驗(yàn),Person的初始化函數(shù)中的self.height = 160也應(yīng)該被Girl所繼承過來。然后在重寫的about方法中,就是用self.height。

實(shí)例化類Girl,并執(zhí)行cang.about("canglaoshi"),試圖打印出一句話canglaoshi is a hot girl, she is about 160, and her bereast is 90。保存程序,運(yùn)行之:

$ python 20903.py 
Traceback (most recent call last):
  File "20903.py", line 22, in <module>
    cang.about("canglaoshi")
  File "20903.py", line 18, in about
    print "{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast)
AttributeError: 'Girl' object has no attribute 'height'

報(bào)錯(cuò)!

程序員有一句名言:不求最好,但求報(bào)錯(cuò)。報(bào)錯(cuò)不是壞事,是我們長(zhǎng)經(jīng)驗(yàn)的時(shí)候,是在告訴我們,那么做不對(duì)。

重要的是看報(bào)錯(cuò)信息。就是我們要打印的那句話出問題了,報(bào)錯(cuò)信息顯示self.height是不存在的。也就是說類Girl沒有從Person中繼承過來這個(gè)屬性。

原因是什么?仔細(xì)觀察類Girl,會(huì)發(fā)現(xiàn),除了剛才強(qiáng)調(diào)的about方法重寫了,__init__方法,也被重寫了。不要認(rèn)為它的名字模樣奇怪,就不把它看做類中的方法(函數(shù)),它跟類Person中的__init__重名了,也同樣是重寫了那個(gè)初始化函數(shù)。

這就提出了一個(gè)問題。因?yàn)樵谧宇愔兄貙懥四硞€(gè)方法之后,父類中同樣的方法被遮蓋了。那么如何再把父類的該方法調(diào)出來使用呢?縱然被遮蓋了,應(yīng)該還是存在的,不要浪費(fèi)了呀。

python中有這樣一種方法,這種方式是被提倡的方法:super函數(shù)。

#!/usr/bin/env python
# coding=utf-8

__metaclass__ = type

class Person:
    def __init__(self):
        self.height = 160

    def about(self, name):
        print "{} is about {}".format(name, self.height)

class Girl(Person):
    def __init__(self):
        super(Girl, self).__init__()
        self.breast = 90

    def about(self, name):
        print "{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast)
        super(Girl, self).about(name)

if __name__ == "__main__":
    cang = Girl()
    cang.about("canglaoshi")

在子類中,__init__方法重寫了,為了調(diào)用父類同方法,使用super(Girl, self).__init__()的方式。super函數(shù)的參數(shù),第一個(gè)是當(dāng)前子類的類名字,第二個(gè)是self,然后是點(diǎn)號(hào),點(diǎn)號(hào)后面是所要調(diào)用的父類的方法。同樣在子類重寫的about方法中,也可以調(diào)用父類的about方法。

執(zhí)行結(jié)果:

$ python 20903.py 
canglaoshi is a hot girl, she is about 160, and her breast is 90
canglaoshi is about 160

最后要提醒注意:super函數(shù)僅僅適用于新式類。當(dāng)然,你一定是使用的新式類?!跋残聟捙f”是程序員的嗜好。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)