類(lèi)(4)

2018-02-24 15:48 更新

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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)行解釋?zhuān)瑥闹畜w會(huì)繼承的概念和方法。

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

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

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

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

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

多重繼承

所謂多重繼承,就是只某一個(gè)類(lèi)的父類(lèi),不止一個(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è)類(lèi):Person和Girl,然后第三個(gè)類(lèi)HotGirl繼承了這兩個(gè)類(lèi),注意觀察繼承方法,就是在類(lèi)的名字后面的括號(hào)中把所繼承的兩個(gè)類(lèi)的名字寫(xiě)上。但是第三個(gè)類(lèi)中什么方法也沒(méi)有。

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

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

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

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

多重繼承的順序

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

#!/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__是要打印出類(lèi)的繼承順序。從上面清晰看出來(lái)了。如果要執(zhí)行foo()方法,首先看J1,沒(méi)有,看J2,還沒(méi)有,看J1里面的K1,有了,即C==>J1==>J2==>K1;bar()也是按照這個(gè)順序,在J2中就找到了一個(gè)。

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

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

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

super函數(shù)

對(duì)于初始化函數(shù)的繼承,跟一般方法的繼承,還有點(diǎn)不同。可以看下面的例子:

#!/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")

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

實(shí)例化類(lèi)Girl,并執(zhí)行cang.about("canglaoshi"),試圖打印出一句話(huà)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ò)信息。就是我們要打印的那句話(huà)出問(wèn)題了,報(bào)錯(cuò)信息顯示self.height是不存在的。也就是說(shuō)類(lèi)Girl沒(méi)有從Person中繼承過(guò)來(lái)這個(gè)屬性。

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

這就提出了一個(gè)問(wèn)題。因?yàn)樵谧宇?lèi)中重寫(xiě)了某個(gè)方法之后,父類(lèi)中同樣的方法被遮蓋了。那么如何再把父類(lèi)的該方法調(diào)出來(lái)使用呢?縱然被遮蓋了,應(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")

在子類(lèi)中,__init__方法重寫(xiě)了,為了調(diào)用父類(lèi)同方法,使用super(Girl, self).__init__()的方式。super函數(shù)的參數(shù),第一個(gè)是當(dāng)前子類(lèi)的類(lèi)名字,第二個(gè)是self,然后是點(diǎn)號(hào),點(diǎn)號(hào)后面是所要調(diào)用的父類(lèi)的方法。同樣在子類(lèi)重寫(xiě)的about方法中,也可以調(diào)用父類(lèi)的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ù)僅僅適用于新式類(lèi)。當(dāng)然,你一定是使用的新式類(lèi)。“喜新厭舊”是程序員的嗜好。

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)