10.1 面向?qū)ο蟾呒壘幊?/h1>
數(shù)據(jù)封裝、繼承和多態(tài)只是面向?qū)ο蟪绦蛟O(shè)計中最基礎(chǔ)的3個概念。在Python中,面向?qū)ο筮€有很多高級特性,允許我們寫出非常強(qiáng)大的功能。
我們會討論多重繼承、定制類、元類等概念。
1、使用__slots__
正常情況下,當(dāng)我們定義了一個class,創(chuàng)建了一個class的實例后,我們可以給該實例綁定任何屬性和方法,這就是動態(tài)語言的靈活性。先定義class:
然后,嘗試給實例綁定一個屬性:
還可以嘗試給實例綁定一個方法:
但是,給一個實例綁定的方法,對另一個實例是不起作用的:
為了給所有實例都綁定方法,可以給class綁定方法:
給class綁定方法后,所有實例均可調(diào)用:
通常情況下,上面的set_score
方法可以直接定義在class中,但動態(tài)綁定允許我們在程序運行的過程中動態(tài)給class加上功能,這在靜態(tài)語言中很難實現(xiàn)。
但是,如果我們想要限制實例的屬性怎么辦?比如,只允許對Student實例添加name
和age
屬性。
為了達(dá)到限制的目的,Python允許在定義class的時候,定義一個特殊的__slots__
變量,來限制該class實例能添加的屬性:
然后,我們試試:
由于'score'
沒有被放到__slots__
中,所以不能綁定score
屬性,試圖綁定score
將得到AttributeError
的錯誤。
使用__slots__
要注意,__slots__
定義的屬性僅對當(dāng)前類實例起作用,對繼承的子類是不起作用的:
除非在子類中也定義__slots__
,這樣,子類實例允許定義的屬性就是自身的__slots__
加上父類的__slots__
。
2、使用@property
在綁定屬性時,如果我們直接把屬性暴露出去,雖然寫起來很簡單,但是,沒辦法檢查參數(shù),導(dǎo)致可以把成績隨便改:
這顯然不合邏輯。為了限制score的范圍,可以通過一個set_score()
方法來設(shè)置成績,再通過一個get_score()
來獲取成績,這樣,在set_score()
方法里,就可以檢查參數(shù):
現(xiàn)在,對任意的Student實例進(jìn)行操作,就不能隨心所欲地設(shè)置score了:
但是,上面的調(diào)用方法又略顯復(fù)雜,沒有直接用屬性這么直接簡單。
有沒有既能檢查參數(shù),又可以用類似屬性這樣簡單的方式來訪問類的變量呢?對于追求完美的Python程序員來說,這是必須要做到的!
還記得裝飾器(decorator)可以給函數(shù)動態(tài)加上功能嗎?對于類的方法,裝飾器一樣起作用。Python內(nèi)置的@property
裝飾器就是負(fù)責(zé)把一個方法變成屬性調(diào)用的:
@property
的實現(xiàn)比較復(fù)雜,我們先考察如何使用。把一個getter方法變成屬性,只需要加上@property
就可以了,此時,@property
本身又創(chuàng)建了另一個裝飾器@score.setter
,負(fù)責(zé)把一個setter方法變成屬性賦值,于是,我們就擁有一個可控的屬性操作:
注意到這個神奇的@property
,我們在對實例屬性操作的時候,就知道該屬性很可能不是直接暴露的,而是通過getter和setter方法來實現(xiàn)的。
還可以定義只讀屬性,只定義getter方法,不定義setter方法就是一個只讀屬性:
上面的birth
是可讀寫屬性,而age
就是一個只讀屬性,因為age
可以根據(jù)birth
和當(dāng)前時間計算出來。
要特別注意:屬性的方法名不要和實例變量重名。例如,以下的代碼是錯誤的:
這是因為調(diào)用s.birth
時,首先轉(zhuǎn)換為方法調(diào)用,在執(zhí)行return self.birth
時,又視為訪問self
的屬性,于是又轉(zhuǎn)換為方法調(diào)用,造成無限遞歸,最終導(dǎo)致棧溢出報錯RecursionError
。
小結(jié)
@property
廣泛應(yīng)用在類的定義中,可以讓調(diào)用者寫出簡短的代碼,同時保證對參數(shù)進(jìn)行必要的檢查,這樣,程序運行時就減少了出錯的可能性。