動態屬性與特性
- 方法(method)只是一種可以被呼叫的屬性(attribute)
- 特性(property)可以用來代替公開的資料屬性,不會變動到類別介面
- 編寫動態屬性,是框架作者會採取的一種中繼編程(metaprogramming)
- 從任意來源產生或模擬動態屬性名稱,都要處理一個問題;原始資料中的 key 可能不適合當成屬性名稱,例如 key 是關鍵字(
keyword.iskeyword()
)或非法的識別符(s.isidentifier()
)
範例:使用動態屬性來探索 JSON 格式的資料
透過遞迴來建構,可自動處理嵌套的映射與串列
留意這裡沒有對查詢進行任何快取或轉換
建構實例的特殊方法是類別方法 __new__
,他可以回傳完全不同的實例,在這種情況下解譯器不會呼叫 __init__
以下為建構實例的虛擬程式
1 2 3 4 5 6 |
def obj_maker(the_class, some_arg): obj = the_class.__new__(some_arg) if isinstance(obj, the_class): the_class.__init__(obj, some_arg) return obj |
- 內建模組
shelve
用來提供pickle
存儲(因為醃菜罐會放在架子上面) shelve.Shelf
是一個簡單的 key-value 物件資料庫(繼承abc.MutableMapping
),支援dbm
模組,同時是一個 Context Manager,提供一些 I/O 管理方法,其儲存的 value 必須是pickle
可以處理的物件
屬性存放在物件的 __dict__
,以下示範一種常見的快捷方式來建構多個屬性
1 2 3 4 |
class Example: def __init__(self, **kwargs): self.__dict__.update(kwargs) |
特性將常被用來強制執行商業規則(business rule),將公用的屬性改成以 getter/setter 管理,而不影響介面
雖然 property
經常被當作修飾器使用,但其實它也是個類別,也是可以被呼叫的
以下演示不使用修飾器的 getter/setter 語法
以此語法來編寫泛函的特性 Factory
obj.attr
的搜尋其實是從類別(obj.__class__
)開始的,如果該類別沒有一個叫做 attr
的特性,才回接續查找 obj 實例本身(obj.__dict__
)
這個規則不但可以套用在特性,也適用於整個描述器(descriptors)種類
__slots__
是一個字串的 tuple,用來限制類別實例可以擁有的屬性,如果它不包含__dict__
,實例將不會有自己的__dict__
(vars()
無法處理這種情況)- 在實務上,只有
__setattr__
、__getattribute__
(有定義的話,優先於類別、超類別的搜尋及__getattr__
)會被無條件地呼叫,因此更難正確地使用。使用特性或描述器會比定義這些特殊方法更不容易出錯 - 在實務上,刪除屬性不是經常做的事情,刪除特性更是罕見,但是 Python 支援這件事
- 「統一存取原則」在理想的世界中雖然是非常合理的,但 API 的使用者可能會想要知道該存取是否會付出昂貴的代價或浪費太多時間
- 在 Java 的世界每一個人都同意屬性必須是 private
- 在 Python,類別與函式在很多情況下是可互換的,這不但是因為 Python 沒有 new 運算子,也是因為它有特殊方法
__new__
,可將類別轉換成製造各種物件的工廠
屬性描述器
- 描述器是一種類別,能在多個屬性上,重複利用同一個存取邏輯,僅基於協定,可選擇實作
__get__
、__set__
、__delete__
- 描述器的典型範例是 Django 的 ModelField
透過類別屬性的 auto increment,在使用端省去屬性名稱
這種方式並不是很優雅,也較難除錯,較佳的作法須要類別修飾器或中繼類別
可以透過更高階的 getattr
、setattr
來存取值,而不一定要直接存取 instance.__dict__
發出例外時,實作的細節資訊應該適時省略,因為看到內部存取名稱為 _Quentity#123
會造成困惑
- Python 處理屬性的方式,有一個重要的不對稱性:透過實例讀取屬性通常會回傳實例中定義的屬性,但如果沒有,就會讀取類別屬性;另一方面,對實例中的屬性賦值,通常會在實例中建立屬性,完全不影響類別
- 實作
__set__
的描述器稱為覆寫描述器(overriding descriptor) - 特性也是一種覆寫描述器:如果你沒有提供 setter,預設的
__set__
會發出 AttributeError 來提醒該屬性是唯讀的 - 覆寫描述器也稱為資料描述器(data descriptor)、強制描述器(enforced descriptor)
- 非覆寫描述器也被稱為非資料描述器(nondata descriptor)、可追隨描述器(shadowable descriptor)
- 無論描述器是否是覆寫的,都有可能因為對類別賦值,而破壞描述器對屬性的控制
函式/方法有 __get__
,因為都沒有實作 __set__
,它們都是非覆寫描述器
在類別中的函式會變成一個綁定(bound)的方法
- 被綁定的方法有一個
__self__
屬性,保存的是實例的參考,其__func__
屬性則是保存了原始函式的參考 - 被綁定的方法的運作方式:呼叫
__call__
,其內部呼叫__func__
並傳遞__self__
作為第一個引數 - 唯讀的描述器須要編寫
__set__
,發出 AttributeError - 非覆寫描述器很適合用來做一些高昂的運算,接著在實例上設定一個同名屬性,來快取結果(第一次會呼叫描述器的
__get__
,快取後直接從instance.__dict__
取值) - 解譯器只會在類別本身尋找特殊方法,換句話說,新增一個名為
__getattr__
的實例屬性,神奇地不會破壞屬性原本的存取操作 - 「設計應該要簡單,包括實作與介面。簡單的實作比簡單的介面還重要。簡單是設計時,最需要考慮的地方。」
- 「如果你對於在 Python 中明確使用 self 感到不開心,當你想到 JavaScript 隱式使用 this 這種莫名其妙的語法時,就會覺得好多了。」