描述符用法建議
下面根據剛剛論述的描述符特征給出一些實用的結論。
使用特性以保持簡單
內置的 property 類創建的其實是覆蓋型描述符,__set__
方法和
__get__
方法都實現了,即便不定義設值方法也是如此。特性的
__set__
方法默認拋出 AttributeError: can’t set attribute,
因此創建只讀屬性最簡單的方式是使用特性,這能避免下一條所述的問
題。
只讀描述符必須有__set__
方法
如果使用描述符類實現只讀屬性,要記住,__get__
和 __set__
兩個方法必須都定義,否則,實例的同名屬性會遮蓋描述符。只讀屬性
的 __set__
方法只需拋出 AttributeError 異常,并提供合適的錯誤
消息。
用于驗證的描述符可以只有 __set__
方法
對僅用于驗證的描述符來說,__set__
方法應該檢查 value 參數
獲得的值,如果有效,使用描述符實例的名稱為鍵,直接在實例的
__dict__
屬性中設置。這樣,從實例中讀取同名屬性的速度很快,因
為不用經過 __get__
方法處理。參見示例 20-1 中的代碼。
僅有 __get__
方法的描述符可以實現高效緩存
如果只編寫了 __get__
方法,那么創建的是非覆蓋型描述符。這
種描述符可用于執行某些耗費資源的計算,然后為實例設置同名屬性,
緩存結果。同名實例屬性會遮蓋描述符,因此后續訪問會直接從實例的__dict__ 屬性中獲取值,而不會再觸發描述符的 __get__
方法。
非特殊的方法可以被實例屬性遮蓋
由于函數和方法只實現了 __get__
方法,它們不會處理同名實例
屬性的賦值操作。因此,像 my_obj.the_method = 7 這樣簡單賦值之
后,后續通過該實例訪問 the_method 得到的是數字 7——但是不影響
類或其他實例。然而,特殊方法不受這個問題的影響。解釋器只會在類
中尋找特殊的方法,也就是說,repr(x) 執行的其實是
x.__class__.__repr__(x)
,因此 x 的__repr__
屬性對 repr(x) 方
法調用沒有影響。出于同樣的原因,實例的 __getattr__
屬性不會破
壞常規的屬性訪問規則。
實例的非特殊方法可以被輕松地覆蓋,這聽起來不可靠且容易出錯,可
是在我使用 Python 的 15 年中從未受此困擾。然而,如果要創建大量動
態屬性,屬性名稱從不受自己控制的數據中獲取(像本章前面那樣),
那么你應該知道這種行為;或許你還可以實現某種機制,過濾或轉義動
態屬性的名稱,以維持數據的健全性。
示例 19-6 中的 FrozenJSON 類不會出現實例屬性遮蓋方法的
問題,因為那個類只有幾個特殊方法和一個 build 類方法。只要
通過類訪問,類方法就是安全的,在示例 19-6 中我就是這么調用
FrozenJSON.build 方法的——在示例 19-7 中替換成 __new__
方
法了。Record 類(見示例 19-9 和示例 19-11)及其子類也是安全
的,因為只用到了特殊的方法、類方法、靜態方法和特性。特性是
數據描述符,因此不能被實例屬性覆蓋。
討論特性時講了兩個功能,這里討論的描述符還未涉及,結束本章之前
我們來講講:文檔和對刪除托管屬性的處理。