1. 程式人生 > >Python描述符(__get__,__set__,__delete__)簡介

Python描述符(__get__,__set__,__delete__)簡介

for 原因 python 原理 none 對象屬性 描述符 簡介 訪問

先說定義,這裏直接翻譯官方英文文檔:

  一般來說,描述符是具有“綁定行為”的對象屬性,該對象的屬性訪問將會被描述符協議中的方法覆蓋.這些方法是__get__(),__set__(),和__delete__().如果一個對象定義了這些方法中的任何一個,它就是一個描述符.

接下來對這個定義進行解釋:

  我們訪問一個對象a的屬性x的時候,是這麽調用的:a.x,那麽這種方便的調用方式其實是怎麽工作的呢?

  首先,它會訪問自己的實例名稱空間:

    a.__dict__[‘x‘]

  如果沒有,則會訪問類及超類的名稱空間, 大致上是這個意思:

for cls in
type(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__)簡介