第七章 再谈抽象
第七章 再談抽象
對(duì)象魔法
多態(tài):可對(duì)不同類型的對(duì)象執(zhí)行相同的操作,而這些操作就像“被施了魔法”一樣能夠正常運(yùn)行。(即:無需知道對(duì)象的內(nèi)部細(xì)節(jié)就可使用它)(無需知道對(duì)象所屬的類(對(duì)象的類型)就能調(diào)用其方法)
封裝:對(duì)外部隱藏有關(guān)對(duì)象工作原理的細(xì)節(jié)。(無需知道對(duì)象的構(gòu)造就能使用它)
繼承:可基于通用類創(chuàng)建出專用類。
1,多態(tài)
即便你不知道變量指向的是哪種對(duì)象,也能夠?qū)ζ鋱?zhí)行操作,且操作的行為將隨對(duì)象所屬的類型(類)而異。
2,多態(tài)和方法
與對(duì)象屬性相關(guān)聯(lián)的函數(shù)稱為方法
如果有一個(gè)變量x,你無需知道它是字符串還是列表就能調(diào)用方法count:只要你向這個(gè)方法提供一個(gè)字符作為參數(shù),它就能正常運(yùn)行。count方法返回指定字符串出現(xiàn)的個(gè)數(shù)。
標(biāo)準(zhǔn)庫模塊random包含一個(gè)名為choice的函數(shù),它從序列中隨機(jī)選擇一個(gè)元素。
from random import choice x = choice(['Hello, world!', [1, 2, 'e', 'e', 4]])#x可能包含字符串'Hello, world!',也可能包含列表[1, 2, 'e', 'e', 4] x.count('e')#可能是1,也可能是2多態(tài)形式多樣。每當(dāng)無需知道對(duì)象是什么樣的就能對(duì)其執(zhí)行操作時(shí),都是多態(tài)在起作用。這不僅僅適用于方法,還可以通過內(nèi)置運(yùn)算符和函數(shù)使用多態(tài)。
參數(shù)可以是任何支持加法的對(duì)象
加法運(yùn)算符(+)既可用于數(shù)(這里是整數(shù)),也可用于字符串(以及其他類型的序列)
等價(jià)于下面函數(shù):
def add(x, y): return x + yadd(1,2)#結(jié)果為:3 add('beyond','huangjiaju')#結(jié)果為:'beyondhuangjiaju'編寫一個(gè)函數(shù),通過打印一條消息來指出對(duì)象的長(zhǎng)度
def length_message(x): print("The length of", repr(x), "is", len(x))length_message('beyond')#結(jié)果為:The length of 'beyond' is 6 length_message([1, 2, 3])#結(jié)果為:The length of [1, 2, 3] is 3要破壞多態(tài),唯一的辦法是使用諸如type、issubclass等函數(shù)顯式地執(zhí)行類型檢查,但你應(yīng)盡可能避免以這種方式破壞多態(tài)。
3,封裝
封裝(encapsulation)指的是向外部隱藏不必要的細(xì)節(jié)
屬性是歸屬于對(duì)象的變量,就像方法一樣。
對(duì)象有自己的狀態(tài)。對(duì)象的狀態(tài)由其屬性(如名稱)描述。
對(duì)象的方法可能修改這些屬性,因此對(duì)象將一系列函數(shù)(方法)組合起來,并賦予它們?cè)L問一些變量(屬性)的權(quán)限,而屬性可用于在兩次函數(shù)調(diào)用之間存儲(chǔ)值。
多態(tài)讓你無需知道對(duì)象所屬的類(對(duì)象的類型)就能調(diào)用其方法
而封裝讓你無需知道對(duì)象的構(gòu)造就能使用它。
4,繼承
繼承是另一種偷懶的方式。
如果你已經(jīng)有了一個(gè)類,并要?jiǎng)?chuàng)建一個(gè)與之很像的類(可能只是新增了幾個(gè)方法),可以讓創(chuàng)建的這個(gè)類去繼承已有的類。
類
類到底是什么?
類的定義——一種對(duì)象。
每個(gè)對(duì)象都屬于特定的類,并被稱為該類的實(shí)例。
對(duì)于類的名稱,在Python中,約定使用單數(shù)并將首字母大寫,如Bird和Lark。
類是由其支持的方法定義的,類的所有實(shí)例都有該類的所有方法,因此子類的所有實(shí)例都有超類的所有方法。
創(chuàng)建自定義類
對(duì)huangjiaju調(diào)用set_name和greet時(shí),huangjiaju都會(huì)作為第一個(gè)參數(shù)自動(dòng)傳遞給它們,這就是self。
self很有用,甚至必不可少。如果沒有它,所有的方法都無法訪問對(duì)象本身——要操作的屬性所屬的對(duì)象
屬性、函數(shù)和方法
方法和函數(shù)的區(qū)別表現(xiàn)在于參數(shù)self。
方法(更準(zhǔn)確地說是關(guān)聯(lián)的方法)將其第一個(gè)參數(shù)關(guān)聯(lián)到它所屬的實(shí)例,因此無需提供這個(gè)參數(shù)。
有沒有參數(shù)self并不取決于是否以剛才使用的方式(如instance.method)調(diào)用方法。
當(dāng)然,也完全可以讓另一個(gè)變量指向同一個(gè)方法。
class Band:songer = "huangjiaju"def sing(self):print(self.songer)beyond = Band() beyond.sing()#結(jié)果為:huangjiajubeyondsonger = beyond.sing beyondsonger()#結(jié)果為:huangjiaju私有屬性不能從對(duì)象外部訪問,而只能通過存取器方法(如get_name和set_name)來訪問。
Python沒有為私有屬性提供直接的支持,而要讓方法或?qū)傩猿蔀樗接械?#xff08;不能從外部訪問),只需讓其名稱以兩個(gè)下劃線打頭即可。
從外部不能訪問__huangjiaju,但在類中(如huangjiaqiang中)依然可以使用它。
以兩個(gè)下劃線打頭,這樣的方法類似于其他語言中的標(biāo)準(zhǔn)私有方法。
在類定義中,對(duì)所有以兩個(gè)下劃線打頭的名稱都進(jìn)行轉(zhuǎn)換,即在開頭加上一個(gè)下劃線和類名。
類的命名空間
以下兩條語句大致等價(jià):它們都創(chuàng)建一個(gè)返回參數(shù)平方的函數(shù)
def Hjj(x):return x*xHjq = lambda x:x*xHjj(8)#結(jié)果為:64 Hjq(9)#結(jié)果為:81在class語句中定義的代碼都是在一個(gè)特殊的命名空間(類的命名空間)內(nèi)執(zhí)行的,而類的所有成員都可訪問這個(gè)命名空間。
class NumberCounter:number = 0def init(self):NumberCounter.number += 1m1 = NumberCounter() m1.init() NumberCounter.number#結(jié)果為:1m2 = NumberCounter() m2.init() NumberCounter.number#結(jié)果為:2 ''' 上述代碼在類作用域內(nèi)定義了一個(gè)變量,所有的成員(實(shí)例)都可訪問它, 這里使用它來計(jì)算類實(shí)例的數(shù)量,注意到這里使用了init來初始化所有實(shí)例!!! '''#每個(gè)實(shí)例都可訪問這個(gè)類作用域內(nèi)的變量,就像方法一樣 m1.number#結(jié)果為:1 m2.number#結(jié)果為:2#在一個(gè)實(shí)例中給屬性number賦值 m1.number = "yy" m1.number#結(jié)果為:'yy' m2.number#結(jié)果為:2 #新值被寫入m1的一個(gè)屬性中,這個(gè)屬性遮住了類級(jí)變量。指定超類
子類擴(kuò)展了超類的定義,要指定超類,可在class語句中的類名后加上超類名,并將其用圓括號(hào)括起。
class Filter:def init(self):self.blocked = []def filter(self,sequence):return [x for x in sequence if x not in self.blocked]class SPAMFilter(Filter):#SPAMFilter是Filter的子類def init(self): #重寫超類Filter的方法initself.blocked = ['SPAM']#Filter是一個(gè)過濾序列的通用類。實(shí)際上,它不會(huì)過濾掉任何東西。 f = Filter() f.init() f.filter([1,2,3])#結(jié)果為:[1, 2, 3]#Filter類的用途在于可用作其他類(如將'SPAM'從序列中過濾掉的SPAMFilter類)的基類(超類)。 s = SPAMFilter() s.init() s.filter(['SPAM','SPAM','SPAM','SPAM','beyond','huangjiaju','SPAM'])#結(jié)果為:['beyond', 'huangjiaju']請(qǐng)注意SPAMFilter類的定義中有兩個(gè)要點(diǎn)。
Ⅰ以提供新定義的方式重寫了Filter類中方法init的定義
Ⅱ直接從Filter類繼承了方法filter的定義,因此無需重新編寫其定義
深入探討繼承
要確定一個(gè)類是否是另一個(gè)類的子類,可使用內(nèi)置方法issubclass。
如果你有一個(gè)類,并想知道它的基類,可訪問其特殊屬性__bases__。
要確定對(duì)象是否是特定類的實(shí)例,可使用isinstance。
要獲悉對(duì)象屬于哪個(gè)類,可使用屬性__class__,還可使用type(s)來獲悉其所屬的類。
多個(gè)超類
子類TalkingCalculator本身無所作為,其所有的行為都是從超類那里繼承的。關(guān)鍵是通過從Calculator那里繼承calculate,并從Talker那里繼承talk。這被稱為多重繼承,是一個(gè)功能強(qiáng)大的工具。除非萬不得已,否則應(yīng)避免使用多重繼承,因?yàn)樵谟行┣闆r下,它可能帶來意外的“并發(fā)癥”。
class Calculator:def calulate(self,expression):self.value = eval(expression)class Talker:def talk(self):print('Hi,my value is',self.value)class TalkingCalulator(Calculator,Talker):passyy = TalkingCalulator() yy.calulate('5+2+1*1314') yy.talk()#結(jié)果為:Hi,my value is 1321多個(gè)超類的超類相同時(shí),查找特定方法或?qū)傩詴r(shí)訪問超類的順序稱為方法解析順序(MRO),它使用的算法非常復(fù)雜。
接口和內(nèi)省
接口這一概念與多態(tài)相關(guān)。處理多態(tài)對(duì)象時(shí),你只關(guān)心其接口(協(xié)議)——對(duì)外暴露的方法和屬性
檢查所需的方法是否存在hasattr(對(duì)象名,方法名)
檢查屬性是否是可調(diào)用的 callable(getattr(對(duì)象名, '方法名', None))
getattr(它讓我能夠指定屬性不存在時(shí)使用的默認(rèn)值,這里為None),然后對(duì)返回的對(duì)象調(diào)用callable
setattr與getattr功能相反,可用于設(shè)置對(duì)象的屬性
要查看對(duì)象中存儲(chǔ)的所有值,可檢查其__dict__屬性
抽象基類
Python幾乎都只依賴于鴨子類型,即假設(shè)所有對(duì)象都能完成其工作,同時(shí)偶爾使用hasattr來檢查所需的方法是否存在。
很多其他語言(如Java和Go)都采用顯式指定接口的理念,而有些第三方模塊提供了這種理念的各種實(shí)現(xiàn)。
Python通過引入模塊abc提供了官方解決方案。這個(gè)模塊為所謂的抽象基類提供了支持。標(biāo)準(zhǔn)庫(如模塊collections.abc)提供了多個(gè)很有用的抽象類,有關(guān)模塊abc的詳細(xì)信息。
抽象類是不能(至少是不應(yīng)該)實(shí)例化的類,其職責(zé)是定義子類應(yīng)實(shí)現(xiàn)的一組抽象方法。
關(guān)于面向?qū)ο笤O(shè)計(jì)的一些思考
| 將相關(guān)的東西放在一起。如果一個(gè)函數(shù)操作一個(gè)全局變量,最好將它們作為一個(gè)類的屬性和方法。 |
| 不要讓對(duì)象之間過于親密。方法應(yīng)只關(guān)心其所屬實(shí)例的屬性,對(duì)于其他實(shí)例的狀態(tài),讓它們自己去管理就好了。 |
| 慎用繼承,尤其是多重繼承。繼承有時(shí)很有用,但在有些情況下可能帶來不必要的復(fù)雜性。要正確地使用多重繼承很難,要排除其中的bug更難。 |
| 保持簡(jiǎn)單。讓方法短小緊湊。一般而言,應(yīng)確保大多數(shù)方法都能在30秒內(nèi)讀完并理解。對(duì)于其余的方法,盡可能將其篇幅控制在一頁或一屏內(nèi)。 |
小結(jié)
| 對(duì)象 | 對(duì)象由屬性和方法組成。屬性不過是屬于對(duì)象的變量,而方法是存儲(chǔ)在屬性中的函數(shù)。相比于其他函數(shù),(關(guān)聯(lián)的)方法有一個(gè)不同之處,那就是它總是將其所屬的對(duì)象作為第一個(gè)參數(shù),而這個(gè)參數(shù)通常被命名為self。 |
| 類 | 類表示一組(或一類)對(duì)象,而每個(gè)對(duì)象都屬于特定的類。類的主要任務(wù)是定義其實(shí)例將包含的方法。 |
| 多態(tài) | 多態(tài)指的是能夠同樣地對(duì)待不同類型和類的對(duì)象,即無需知道對(duì)象屬于哪個(gè)類就可調(diào)用其方法。 |
| 封裝 | 對(duì)象可能隱藏(封裝)其內(nèi)部狀態(tài)。在有些語言中,這意味著對(duì)象的狀態(tài)(屬性)只能通過其方法來訪問。在Python中,所有的屬性都是公有的,但直接訪問對(duì)象的狀態(tài)時(shí)程序員應(yīng)謹(jǐn)慎行事,因?yàn)檫@可能在不經(jīng)意間導(dǎo)致狀態(tài)不一致。 |
| 繼承 | 一個(gè)類可以是一個(gè)或多個(gè)類的子類,在這種情況下,子類將繼承超類的所有方法。你可指定多個(gè)超類,通過這樣做可組合正交(獨(dú)立且不相關(guān))的功能。為此,一種常見的做法是使用一個(gè)核心超類以及一個(gè)或多個(gè)混合超類。 |
| 接口和內(nèi)省 | 一般而言,你無需過于深入地研究對(duì)象,而只依賴于多態(tài)來調(diào)用所需的方法。然而,如果要確定對(duì)象包含哪些方法或?qū)傩?#xff0c;有一些函數(shù)可供你用來完成這種工作。 |
| 抽象基類 | 使用模塊abc可創(chuàng)建抽象基類。抽象基類用于指定子類必須提供哪些功能,卻不實(shí)現(xiàn)這些功能。 |
| 面向?qū)ο笤O(shè)計(jì) | 關(guān)于該如何進(jìn)行面向?qū)ο笤O(shè)計(jì)以及是否該采用面向?qū)ο笤O(shè)計(jì),有很多不同的觀點(diǎn)。無論你持什么樣的觀點(diǎn),都必須深入理解問題,進(jìn)而創(chuàng)建出易于理解的設(shè)計(jì)。 |
本章節(jié)介紹的新函數(shù)
| callable(object) | 判斷對(duì)象是否是可調(diào)用的(如是否是函數(shù)或方法) |
| getattr(object,name[,default]) | 獲取屬性的值,還可提供默認(rèn)值 |
| hasattr(object, name) | 確定對(duì)象是否有指定的屬性 |
| isinstance(object, class) | 確定對(duì)象是否是指定類的實(shí)例 |
| issubclass(A, B) | 確定A是否是B的子類 |
| random.choice(sequence) | 從一個(gè)非空序列中隨機(jī)地選擇一個(gè)元素 |
| setattr(object, name, value) | 將對(duì)象的指定屬性設(shè)置為指定的值 |
| type(object) | 返回對(duì)象的類型 |
總結(jié)
- 上一篇: “疲客心易惊”上一句是什么
- 下一篇: 第二章 染色热力学理论单元测验