Python描述符(__get__,__set__,__delete__)簡介
先說定義,這裏直接翻譯官方英文文檔:
一般來說,描述符是具有“綁定行為”的對象屬性,該對象的屬性訪問將會被描述符協議中的方法覆蓋.這些方法是__get__(),__set__(),和__delete__().如果一個對象定義了這些方法中的任何一個,它就是一個描述符.
接下來對這個定義進行解釋:
我們訪問一個對象a的屬性x的時候,是這麽調用的:a.x,那麽這種方便的調用方式其實是怎麽工作的呢?
首先,它會訪問自己的實例名稱空間:
a.__dict__[‘x‘]
如果沒有,則會訪問類及超類的名稱空間, 大致上是這個意思:
for cls intype(a).__mro__: if hasattr(cls, ‘x‘): return cls.__dict__[‘x‘]
但如果該屬性綁定了一個帶有__get__的類的實例化對象,這個時候,b.x的工作方式就與上面不同了:
class Desc: val = 1 def __get__(self, instance, owner): return self.val class B: x = Desc() b = B() # b.x調用路徑:type(b).__dict__[‘x‘].__get__(b, type(b))# 需要註意的一點是,定義了描述符之後,在構造方法裏為同名變量賦值是無效的 print(b.x) >>>1
這是怎麽實現的呢?要解釋清楚這個原理,要先說明一下__getattribute__函數,當我們調用一個屬性的時候,底層其實就是在執行該函數,該函數的工作方式是:
B.x => B.__dict__[‘x‘] => 如果 存在__get__方法 則 B.__dict__[‘x‘].__get__(None, B)
具體代碼如下:
def __getattribute__(self, key): "Emulate type_getattro() in Objects/typeobject.c" v = object.__getattribute__(self, key) if hasattr(v, ‘__get__‘): return v.__get__(None, self) return v
所以, 我們給B.x綁定改的是一個對象,返回的卻是該對象的__get__方法的返回值,重寫這個函數,我們就可以停止描述符的調用.
接下來再解釋__set__:
class Desc: num = 1 def __get__(self, instance, owner): return self.num def __set__(self, instance, value): self.num += value class B: x = Desc() b = B() b.x = 2 print(b.x)
>>>3
我們給b.x賦值為2,結果輸出的b.x則為3,神奇嗎?
這個概念可能不太好理解,其原因是這裏的‘=‘符號被重載了,不再是賦值的意思.
如果B.__dict__[‘x‘]中沒有__set__方法,‘=‘符號則執行其父類的__set__,一般來說,就是正常的賦值.
如果B.__dict__[‘x‘]重寫了__set__方法,‘=‘符號則執行該重寫的方法,即B.__dict__[‘x‘].__set__(None, value)
利用這一特性,我們可以在python程序中創建常量, 只需要在__set__方法裏拋出一個異常即可.
至於 __delete__,在del b.x時會觸發,如果未定義,則報錯
ps: Properties, bound methods, static methods, class methods都是描述符協議的應用.欲知後事如何,請看英文文檔:https://docs.python.org/3/howto/descriptor.html
Python描述符(__get__,__set__,__delete__)簡介