前面學(xué)習(xí)了面向過程和面向?qū)ο蟮母拍睿謱W(xué)習(xí)了Python中類和對(duì)象的相關(guān)知識(shí)牢屋,其中面向?qū)ο笞畲蟮奶卣魇牵悍庋b且预、繼承和多態(tài)。本文就分3大塊內(nèi)容烙无,分別來詳細(xì)學(xué)習(xí)面向?qū)ο笾械娜筇卣鳌?/p>
封裝
封裝的概念來自電氣電子元件锋谐,集成電路的一道工序,后來引用到面向?qū)ο蟮某绦蛟O(shè)計(jì)思想中〗乜幔現(xiàn)實(shí)中處處可見封裝的例子:比如你家的洗衣機(jī)涮拗。
把一臺(tái)洗衣機(jī)看做洗衣機(jī)類的一個(gè)實(shí)例,洗衣機(jī)里有標(biāo)準(zhǔn)洗、速洗三热、精洗鼓择、桶干燥、潔桶等多種功能就漾。作為用戶不需要知道這些功能內(nèi)部的具體實(shí)現(xiàn)呐能,需要某項(xiàng)功能只要選擇對(duì)應(yīng)的功能鍵即可。這就是洗衣機(jī)封裝了以上多種功能抑堡。
相似地摆出,在面向?qū)ο笾校庋b首妖,說白了就是一個(gè)類中抽象出來的靜態(tài)數(shù)據(jù)(即屬性)以及該類的一些功能(即方法)懊蒸。這就是前面學(xué)習(xí)的對(duì)象的屬性和方法。在一個(gè)對(duì)象內(nèi)部悯搔,某些代碼或某些數(shù)據(jù)可以是私有的,不能被外界訪問舌仍。通過這種方式妒貌,對(duì)象對(duì)內(nèi)部數(shù)據(jù)提供了不同級(jí)別的保護(hù),以防止程序中無關(guān)的部分意外的改變或錯(cuò)誤的使用了對(duì)象的私有部分铸豁。這是前面學(xué)習(xí)的訪問權(quán)限灌曙,在封裝特性中,訪問權(quán)限非常重要节芥。
繼承
在現(xiàn)實(shí)生活中一說到繼承在刺,你一定會(huì)想到"子承父業(yè)"這個(gè)詞。因?yàn)檫@種父子關(guān)系头镊,兒子"平白無故地"從父親那里得到了家產(chǎn)基業(yè)等蚣驼。
在面向?qū)ο蟮某绦蛟O(shè)計(jì)里,與此類似相艇。假設(shè)A類繼承自B類颖杏,則A類稱為子類或派生類,B類稱為父類坛芽,也叫基類留储、超類。那么A類就"子承父業(yè)"咙轩,這里的"父業(yè)"获讳,就是父類中的屬性和方法,如此活喊,A類中自然就有了B類中的屬性和方法丐膝。這種繼承關(guān)系使得代碼的復(fù)用性大大提高。
Python 同樣支持類的繼承,如果一種語言不支持繼承尤误,類就沒有什么意義侠畔。不同的開發(fā)語言,繼承的支持程度不一樣损晤。Objective-C软棺、Java等是單繼承,而Python尤勋、C++等是支持多繼承的喘落。
前面在學(xué)習(xí)python中類的定義時(shí),當(dāng)時(shí)這樣解釋說明:類的定義需要關(guān)鍵字class最冰,class后面緊接著是類名瘦棋,即ClassName,類名通常是大寫開頭的單詞暖哨。緊接著是(object)赌朋,表示該類是從哪個(gè)類繼承下來的,通常篇裁,如果沒有合適的繼承類沛慢,就使用object類,這是所有類最終都會(huì)繼承的類达布。<statement-1-N>表示類中的屬性或方法团甲。
class ClassName(object):
<statement-1>
·
·
·
<statement-N>
既然,python是支持多繼承的黍聂,那么類名后面的()
里躺苦,是可以有多個(gè)類的,即:(BaseClass1, BaseClass2, BaseClass3...)
产还。需要注意圓括號(hào)中父類的順序匹厘,若是父類中有相同的方法名,而在子類使用時(shí)未指定脐区,python從左至右搜索集乔,即方法在子類中未找到時(shí),從左到右查找基類中是否包含方法坡椒。注意:方法的查找順序這一點(diǎn)要十分清楚扰路。
BaseClassName(基類名)這種寫法必須與派生類定義在一個(gè)作用域內(nèi)倔叼。通常汗唱,如果基類定義在另一個(gè)模塊中時(shí)這一點(diǎn)非常有用:
class ClassName(modname.BaseClassName):
我們假設(shè)定義了三個(gè)類:Animal動(dòng)物類,Dog狗類丈攒,Cat貓類哩罪,模塊結(jié)構(gòu)分別如下:
Animal源碼如下:
# 定義一個(gè)動(dòng)物類Animal
class Animal(object):
legs = '' # 腿的數(shù)量
color = '' # 毛色
def eat(self):
print("Animal吃東西")
def sleep(self):
print("Animal睡覺")
def walk(self):
print("Animal走路")
Dog狗類授霸,繼承自Animal類,源碼:
from extends import animal
# 定義一個(gè)狗類际插,繼承自動(dòng)物類Animal
class Dog(animal.Animal):
pass
Cat貓類繼承自Animal類碘耳,源碼如下:
from extends import animal
# 定義一個(gè)貓類Cat,繼承自動(dòng)物類Animal
class Cat(animal.Animal):
pass
測(cè)試模塊extends_test.py
框弛,分別創(chuàng)建了一個(gè)貓類對(duì)象c辛辨、狗類對(duì)象d,分別調(diào)用c瑟枫、d對(duì)象吃的方法eat()
斗搞,源碼如下:
from extends import cat, dog
# 創(chuàng)建一個(gè)貓類對(duì)象
c = cat.Cat()
# 調(diào)用吃的方法
c.eat()
print('------------------')
# 創(chuàng)建一個(gè)狗類對(duì)象
d = dog.Dog()
# 調(diào)用吃的方法
d.eat()
運(yùn)行結(jié)果:
Animal吃東西
------------------
Animal吃東西
可以看到,都打印出了Animal類中eat()
方法中的內(nèi)容慷妙。這樣我們通過繼承關(guān)系僻焚,讓Dog\Cat兩個(gè)類什么也不寫就有了父類中的所有的屬性和方法。
多繼承
我們假設(shè)定義了4個(gè)類:Animal動(dòng)物類膝擂,哺乳動(dòng)物類Mammal虑啤、Dog狗類,Cat貓類架馋,模塊結(jié)構(gòu)分別如下:
Animal動(dòng)物類咐旧,哺乳動(dòng)物類Mammal兩個(gè)類,將作為Dog狗類绩蜻,Cat貓類的父類。
Animal源碼如下:
# 定義一個(gè)動(dòng)物類Animal
class Animal(object):
legs = '' # 腿的數(shù)量
color = '' # 毛色
def eat(self):
print("Animal吃東西")
def sleep(self):
print("Animal睡覺")
def walk(self):
print("Animal走路")
Mammal哺乳動(dòng)物類室埋,源碼如下:
# 定義一個(gè)動(dòng)物類Mammal
class Mammal(object):
# 哺育幼崽的方法
def feed(self):
print("用母乳哺育幼崽子")
Dog狗類办绝,繼承自Animal類,源碼如下:
from extends import animal, mammal
# 定義一個(gè)狗類姚淆,繼承自動(dòng)物類Animal,哺乳動(dòng)物類Mammal
class Dog(animal.Animal, mammal.Mammal):
pass
Cat貓類繼承自Animal類孕蝉,源碼如下:
from extends import animal, mammal
# 定義一個(gè)貓類Cat,繼承自動(dòng)物類Animal腌逢,哺乳動(dòng)物類Mammal
class Cat(animal.Animal, mammal.Mammal):
pass
測(cè)試模塊extends_test.py
降淮,分別創(chuàng)建了一個(gè)貓類對(duì)象c、狗類對(duì)象d搏讶,分別調(diào)用c佳鳖、d對(duì)象吃的方法eat()
,源碼如下:
from extends import cat, dog
# 創(chuàng)建一個(gè)貓類對(duì)象
c = cat.Cat()
# 調(diào)用吃的方法
c.eat()
# 調(diào)用Mammal類中的方法
c.feed()
print('------------------')
# 創(chuàng)建一個(gè)狗類對(duì)象
d = dog.Dog()
# 調(diào)用Animal中吃的方法:eat()
d.eat()
# 調(diào)用Mammal類中的方法:feed()
d.feed()
運(yùn)行結(jié)果:
Animal吃東西
用母乳哺育幼崽子
------------------
Animal吃東西
用母乳哺育幼崽子
拓展
在Java中媒惕,使用extend
關(guān)鍵字表示繼承系吩,Object是Java中所有類的基類,僅支持單繼承妒蔚。例如:
class A extend Object{
}
在Objective-C中穿挨,則使用:
表示繼承月弛。NSObject是OC中所有類的基類,且僅支持單繼承科盛。例如:
@interface MyClass : NSObject {
int memberVar1; // 實(shí)體變量
id memberVar2;
}
+(return_type) class_method; // + 開頭表示類方法
-(return_type) instance_method1; // - 開頭表示實(shí)例方法
接下來要學(xué)習(xí)的面向?qū)ο蟮亩鄳B(tài)帽衙,正是在繼承的基礎(chǔ)上才有的特性:當(dāng)子類和父類有了相同名稱的方法時(shí),就是多態(tài)了贞绵。
多態(tài)
多態(tài)特性涉及到父子類以及父類之間同名函數(shù)的調(diào)用順序問題厉萝,所以前面提到了:一定要注意Python在自定義類時(shí),類名后面的()
圓括號(hào)中父類的順序但壮。若是父類中有相同的方法名冀泻,而在子類使用時(shí)未指定,python從左至右搜索蜡饵,即方法在子類中未找到時(shí)弹渔,從左到右查找基類中是否包含方法。
注意:方法的查找順序這一點(diǎn)要十分清楚溯祸。
使用上面同樣的類肢专,我們定義了4個(gè)類:Animal動(dòng)物類,哺乳動(dòng)物類Mammal焦辅、Dog狗類博杖,Cat貓類,模塊結(jié)構(gòu)分別如下:
Animal動(dòng)物類筷登,哺乳動(dòng)物類Mammal兩個(gè)類剃根,將作為Dog狗類,Cat貓類的父類前方。
Animal源碼如下:
# 定義一個(gè)動(dòng)物類Animal
class Animal(object):
legs = '' # 腿的數(shù)量
color = '' # 毛色
def eat(self):
print("Animal吃東西")
def sleep(self):
print("Animal睡覺")
def walk(self):
print("Animal走路")
Mammal哺乳動(dòng)物類狈醉,源碼如下:
# 定義一個(gè)動(dòng)物類Mammal
class Mammal(object):
def feed(self):
print("用母乳哺育幼崽子")
def eat(self):
print("哺乳動(dòng)物-吃東西")
def sleep(self):
print("哺乳動(dòng)物-睡覺")
def walk(self):
print("哺乳動(dòng)物-走路")
Dog狗類,繼承自Animal類惠险,源碼如下:
from extends import animal, mammal
# 定義一個(gè)狗類苗傅,繼承自動(dòng)物類Animal,哺乳動(dòng)物類Mammal
class Dog(animal.Animal, mammal.Mammal):
def eat(self):
print("小狗啃骨頭")
def sleep(self):
print("小狗警惕地睡覺")
def walk(self):
print("小狗走路")
Cat貓類繼承自Animal類,源碼如下:
from extends import animal, mammal
# 定義一個(gè)貓類Cat班巩,繼承自動(dòng)物類Animal渣慕,哺乳動(dòng)物類Mammal
class Cat(animal.Animal, mammal.Mammal):
def eat(self):
print("小貓吃魚")
def sleep(self):
print("小貓呼呼睡覺")
def walk(self):
print("小貓走貓步")
可以看到,Animal動(dòng)物類抱慌,哺乳動(dòng)物類Mammal兩個(gè)類逊桦,作為Dog狗類,Cat貓類的父類抑进,并且每個(gè)類中都有同名不同實(shí)現(xiàn)的方法:eat()卫袒、sleep()、walk()
单匣。
此時(shí)再在測(cè)試模塊extends_test.py
夕凝,分別創(chuàng)建了一個(gè)貓類對(duì)象c宝穗、狗類對(duì)象d,分別調(diào)用c码秉、d對(duì)象吃的方法eat()
逮矛,源碼如下:
from extends import cat, dog
# 創(chuàng)建一個(gè)貓類對(duì)象
c = cat.Cat()
# 調(diào)用吃的方法
c.eat()
print('------------------')
# 創(chuàng)建一個(gè)狗類對(duì)象
d = dog.Dog()
# 調(diào)用Animal中吃的方法:eat()
d.eat()
運(yùn)行結(jié)果如下:
小貓吃魚
------------------
小狗啃骨頭
子類的方法覆蓋了父類中同名的方法,優(yōu)先調(diào)用子類中同名的方法转砖。
假設(shè)须鼎,現(xiàn)在需要調(diào)用父類中的方法,而不是調(diào)用子類中的方法府蔗,則可以這么做:
# 上接
# 調(diào)用d對(duì)象父類中的eat()方法
super(dog.Dog, d).eat()
運(yùn)行結(jié)果:
Animal吃東西
深入理解多態(tài)
要理解什么是多態(tài)晋控,我們首先要對(duì)數(shù)據(jù)類型再作一點(diǎn)說明。當(dāng)我們定義一個(gè)class的時(shí)候姓赤,我們實(shí)際上就定義了一種數(shù)據(jù)類型赡译。我們定義的數(shù)據(jù)類型和Python自帶的數(shù)據(jù)類型,比如str不铆、list蝌焚、dict沒什么兩樣。比如上面的用到的對(duì)象:
my_list = list() # my_list 是list類型
a = Animal() # a是Animal類型
d = Dog() # d是Dog類型
isinstance(對(duì)象, 類型)
isinstance(變量, 類型)
用來判斷一個(gè)變量是否是該類型或其子類的實(shí)例誓斥。注意如果該對(duì)象是該類的子類的對(duì)象也返回True只洒。
# 創(chuàng)建一個(gè)貓類對(duì)象
c = cat.Cat()
# 判斷貓對(duì)象c是否是Cat類型
print('貓c是Cat類型:',isinstance(c, cat.Cat))
# 判斷貓對(duì)象c是否是Cat類型
print('貓c是Animal類型:',isinstance(c, animal.Animal))
運(yùn)行結(jié)果:
貓c是Cat類型: True
貓c是Animal類型: True
這和現(xiàn)實(shí)是匹配的:貓是貓類型,貓是動(dòng)物劳坑。但是毕谴,反過來,動(dòng)物是貓距芬,就不行了涝开。
# 創(chuàng)建一個(gè)Animal動(dòng)物對(duì)象
a = animal.Animal()
# 判斷動(dòng)物對(duì)象a是否是Cat類型
print('動(dòng)物a是Cat類型:', isinstance(a, cat.Cat))
# 判斷動(dòng)物對(duì)象a是否是Animal類型
print('動(dòng)物a是Animal類型:', isinstance(a, animal.Animal))
運(yùn)行結(jié)果:
動(dòng)物a是Cat類型: False
動(dòng)物a是Animal類型: True
知道了以上知識(shí),我們?cè)賹懸粋€(gè)方法:
# 定義一個(gè)動(dòng)物吃東西的方法
def animal_eat(animal):
animal.eat()
# 調(diào)用動(dòng)物吃東西的方法蔑穴,傳入不同的對(duì)象
animal_eat(dog.Dog())
animal_eat(cat.Cat())
運(yùn)行結(jié)果:
小狗啃骨頭
小貓吃魚
至此發(fā)現(xiàn)什么沒?如果沒有惧浴,那么現(xiàn)在存和,如果我們?cè)俣x一個(gè)Wolf類型,也繼承自Animal:
from extends import animal, mammal
# 定義一個(gè)狼類衷旅,繼承自動(dòng)物類Animal,哺乳動(dòng)物類Mammal
class Wolf(animal.Animal, mammal.Mammal):
def eat(self):
print("狼吃羊")
def sleep(self):
print("狼警惕地睡覺")
def walk(self):
print("狼飛奔")
在測(cè)試模塊extends_test.py
捐腿,導(dǎo)入wolf模塊,并創(chuàng)建一個(gè)wolf對(duì)象柿顶,源碼如下:
from extends import cat, dog, animal, wolf
# 定義一個(gè)動(dòng)物吃東西的方法
def animal_eat(animal):
animal.eat()
animal_eat(dog.Dog())
animal_eat(cat.Cat())
# 傳入狼對(duì)象
animal_eat(wolf.Wolf())
運(yùn)行結(jié)果:
小狗啃骨頭
小貓吃魚
狼吃羊
你會(huì)發(fā)現(xiàn)茄袖,新增一個(gè)Animal的子類,不必對(duì)animal_eat()做任何修改嘁锯,實(shí)際上宪祥,任何依賴Animal作為參數(shù)的函數(shù)或者方法都可以不加修改地正常運(yùn)行聂薪,原因就在于多態(tài)。
多態(tài)的好處
多態(tài)的好處就是蝗羊,當(dāng)我們?cè)谛枰獋魅隓og藏澳、Cat、Wolf……時(shí)耀找,我們只需要接收Animal類型就可以了翔悠,因?yàn)镈og、Cat野芒、Wolf……都是Animal類型蓄愁,然后,按照Animal類型進(jìn)行操作即可狞悲。由于Animal類型有eat()
方法撮抓,因此,傳入的任意類型效诅,只要是Animal類或者子類胀滚,就會(huì)自動(dòng)調(diào)用實(shí)際類型的eat()
方法,這就是多態(tài)的好處乱投。
對(duì)于一個(gè)變量咽笼,我們只需要知道它是Animal類型,無需確切地知道它的子類型戚炫,就可以放心地調(diào)用eat()
方法剑刑,而具體調(diào)用的eat()
方法是作用在Animal、Dog双肤、Cat還是Wolf對(duì)象上施掏,由運(yùn)行時(shí)該對(duì)象的確切類型決定,這就是多態(tài)真正的威力:調(diào)用方只管調(diào)用茅糜,不管細(xì)節(jié)七芭,而當(dāng)我們新增一種Animal的子類時(shí),只要確保eat()
方法編寫正確蔑赘,不用管原來的代碼是如何調(diào)用的狸驳。
拓展:靜態(tài)語言 & 動(dòng)態(tài)語言
對(duì)于靜態(tài)語言來說,例如Java缩赛,如果需要傳入Animal類型耙箍,則傳入的對(duì)象必須是Animal類型或者它的子類,否則酥馍,將無法調(diào)用eat()
方法辩昆。
對(duì)于Python這樣的動(dòng)態(tài)語言來說,則不一定需要傳入Animal類型旨袒。我們只需要保證傳入的對(duì)象有一個(gè)eat()
方法就可以了汁针。例如术辐,定義一個(gè)帶有eat()方法的黑洞類:
# 定義一個(gè)帶有eat()方法的黑洞類
class BlackHole(object):
def eat(self):
print("黑洞吞噬一切東西,包括光~")
from extends import cat, dog, animal, wolf
# 定義一個(gè)動(dòng)物吃東西的方法
def animal_eat(animal):
animal.eat()
# 傳入黑洞對(duì)象
animal_eat(BlackHole())
運(yùn)行結(jié)果:
黑洞吞噬一切東西扇丛,包括光~
這就是動(dòng)態(tài)語言的“鴨子類型”术吗,它并不要求嚴(yán)格的繼承體系,一個(gè)對(duì)象只要“看起來像鴨子帆精,走起路來像鴨子”较屿,那它就可以被看做是鴨子。
Python的“file-like object“就是一種鴨子類型卓练。對(duì)真正的文件對(duì)象隘蝎,它有一個(gè)read()方法,返回其內(nèi)容襟企。但是嘱么,許多對(duì)象,只要有read()方法顽悼,都被視為“file-like object“曼振。許多函數(shù)接收的參數(shù)就是“file-like object“,你不一定要傳入真正的文件對(duì)象蔚龙,完全可以傳入任何實(shí)現(xiàn)了read()方法的對(duì)象冰评。
小結(jié)
本文詳細(xì)學(xué)習(xí)了Python中面向?qū)ο蟮?大特性:封裝、繼承木羹、多態(tài)甲雅,以及拓展了python中的鴨子類型。這三大特性是任何面向?qū)ο笳Z言的基礎(chǔ)知識(shí)坑填,要深刻理解抛人。
更多了解,可關(guān)注公眾號(hào):人人懂編程