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