鍍金池/ 教程/ Python/ 類(lèi)(4)
標(biāo)準(zhǔn)庫(kù) (4)
如何成為 Python 高手
標(biāo)準(zhǔn)庫(kù) (6)
標(biāo)準(zhǔn)庫(kù) (3)
類(lèi)(2)
Pandas 使用 (2)
xml
用 tornado 做網(wǎng)站 (5)
文件(1)
練習(xí)
列表(3)
從小工到專(zhuān)家
除法
錯(cuò)誤和異常 (2)
函數(shù)(1)
用 tornado 做網(wǎng)站 (7)
為做網(wǎng)站而準(zhǔn)備
函數(shù)練習(xí)
標(biāo)準(zhǔn)庫(kù) (8)
Pandas 使用 (1)
回顧 list 和 str
字典(1)
用 tornado 做網(wǎng)站 (3)
字符串(1)
函數(shù)(2)
寫(xiě)一個(gè)簡(jiǎn)單的程序
將數(shù)據(jù)存入文件
語(yǔ)句(5)
SQLite 數(shù)據(jù)庫(kù)
集成開(kāi)發(fā)環(huán)境(IDE)
集合(1)
類(lèi)(1)
用 tornado 做網(wǎng)站 (6)
用 tornado 做網(wǎng)站 (2)
自省
語(yǔ)句(4)
錯(cuò)誤和異常 (1)
用 tornado 做網(wǎng)站 (4)
集合(2)
列表(1)
標(biāo)準(zhǔn)庫(kù) (1)
生成器
mysql 數(shù)據(jù)庫(kù) (1)
第三方庫(kù)
實(shí)戰(zhàn)
運(yùn)算符
類(lèi)(3)
字典(2)
語(yǔ)句(1)
數(shù)和四則運(yùn)算
語(yǔ)句(2)
文件(2)
MySQL 數(shù)據(jù)庫(kù) (2)
電子表格
迭代器
mongodb 數(shù)據(jù)庫(kù) (1)
特殊方法 (2)
特殊方法 (1)
字符編碼
編寫(xiě)模塊
用 tornado 做網(wǎng)站 (1)
標(biāo)準(zhǔn)庫(kù) (5)
函數(shù)(4)
類(lèi)(5)
字符串(2)
關(guān)于 Python 的故事
函數(shù)(3)
字符串(4)
處理股票數(shù)據(jù)
常用數(shù)學(xué)函數(shù)和運(yùn)算優(yōu)先級(jí)
字符串(3)
為計(jì)算做準(zhǔn)備
多態(tài)和封裝
類(lèi)(4)
迭代
語(yǔ)句(3)
錯(cuò)誤和異常 (3)
分析 Hello
Python 安裝
標(biāo)準(zhǔn)庫(kù) (2)
列表(2)
元組

類(lèi)(4)

本節(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)榇嬖?Duck 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, n):
        self.length = n

    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)不同??梢钥聪旅娴睦樱?/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("wangguniang")

在上面這段程序中,類(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("wangguniang"),試圖打印出一句話 wangguniang 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("wangguniang")
  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ò)信息。就是我們要打印的那句話出問(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("wangguniang")

在子類(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 
wangguniang is a hot girl, she is about 160, and her breast is 90
wangguniang is about 160

最后要提醒注意:super 函數(shù)僅僅適用于新式類(lèi)。當(dāng)然,你一定是使用的新式類(lèi)。“喜新厭舊”是程序員的嗜好。


總目錄   |   上節(jié):類(lèi)(3)   |   下節(jié):類(lèi)(5)

如果你認(rèn)為有必要打賞我,請(qǐng)通過(guò)支付寶:qiwsir@126.com,不勝感激。

上一篇:列表(2)下一篇:語(yǔ)句(3)