python中描述符(descriptor類)詳解


1、什么是描述符?

  python描述符是一個“綁定行為”的對象屬性,在描述符協議中,它可以通過方法重寫屬性的訪問。這些方法有 __get__(), __set__(), 和__delete__()。如果這些方法中的任何一個被定義在一個對象中,這個對象就是一個描述符。

  以上為官方定義,純粹為了裝逼使用,一般人看這些定義都有一種問候祖先的沖動!!!!(WQNMLGB)

2、講解描述符前,先看一下魔法方法:__dict__ (每個對象均具備該方法)(對象包括類對象,實例對象~~)

  作用:字典類型,存放本對象的屬性,key(鍵)即為屬性名,value(值)即為屬性的值,形式為{attr_key : attr_value}

  對象屬性的訪問順序:

 

  ①.實例屬性

 

  ②.類屬性

 

  ③.父類屬性

 

  ④.__getattr__()方法

 

  以上順序,切記切記!!!

 例子:

class Test(object):     #object是所有類的基類!!
    cls_val = 1       #類屬性 class_val 
    def __init__(self):
        self.ins_val = 10  #實例屬性  instance_val

        
>>> t=Test()
>>> Test.__dict__
mappingproxy({'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})
>>> t.__dict__ {'ins_val': 10} #更改實例t的屬性cls_val,只是為對象新增了該 實例屬性,並不影響類Test的 類屬性cls_val
>>> t.cls_val = 20
>>> t.__dict__
{'ins_val': 10, 'cls_val': 20} >>> Test.__dict__
mappingproxy({'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})

#更改了類Test的屬性cls_val的值,由於事先增加了實例t的cls_val屬性,因此也不會改變實例的cls_val值(井水不犯河水) >>> Test.cls_val = 30 >>> t.__dict__ {'ins_val': 10, 'cls_val': 20} >>> Test.__dict__ mappingproxy({'__module__': '__main__', 'cls_val': 30, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__w
eakref__
': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})

從以上代碼可知,類屬性和實例屬性不一樣!雖然可以同名,但代表的是不同的兩個屬性!!

 

 3、魔法方法:__get__(), __set__(), __delete__()

  方法的原型為:

  ① __get__(self, instance, owner)

  ② __set__(self, instance, value)

  ③ __del__(self, instance)

  那么以上的 self, instance owner到底指啥么呢?

  首先我們先看一段代碼:

#代碼 1

class Desc(object):    
    
    def __get__(self, instance, owner):
        print("__get__...")
        print("self : \t\t", self)
        print("instance : \t", instance)
        print("owner : \t", owner)
        print('='*40, "\n")
        
    def __set__(self, instance, value):
        print('__set__...')
        print("self : \t\t", self)
        print("instance : \t", instance)
        print("value : \t", value)
        print('='*40, "\n")


class TestDesc(object):
    x = Desc()    #x是Desc類的實例對象 ,同時x也是TestDesc的類屬性 ;    Desc()實例化一個對象,並用x對其引用,所以x就是Desc類的實例化對象

#以下為測試代碼 t = TestDesc()    #t是TestDesc類的實例對象 t.x #以下為輸出信息: __get__... self : <__main__.Desc object at 0x0000000002B0B828> instance : <__main__.TestDesc object at 0x0000000002B0BA20> owner : <class '__main__.TestDesc'> ========================================

由輸出信息可以知道:

  ① self:   Desc的實例對象,其實就是TestDesc的類屬性x

  ② instance: TestDesc的實例對象,其實就是t

  ③ owner:  即誰擁有這些東西,當然是 TestDesc這個類;它是最高統治者,其他的一些都是包含在它的內部或者由它生出來的

 

其實,Desc類就是是一個描述符(描述符是一個類哦),為啥呢?因為類Desc定義了方法 __get__, __set__. __delete__

所以,某個類,只要是內部定義了方法 __get__, __set__, __delete__ 中的一個或多個,就可以稱為描述符(^_^,簡單吧)

 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

到這兒,還是有很多問題的!

問題1. 為什么訪問 t.x的時候,會直接去調用描述符的 __get__() 方法呢?

    答:t為實例,訪問t.x時,根據常規順序,

      首先:訪問Owner 即 TestDesc的__getattribute__()方法(其實就是 TestDesc.__getattribute__()) ,訪問實例屬性 ; 發現沒有 , 然后去類TestDesc中訪問,找到了類屬性x

      其次:判斷類屬性 x 為一個描述符,此時,它就會做一些變動了,將 TestDesc.x 轉化為   TestDesc. __dict__['x'].__get__(None, TestDesc)  來訪問

      然后:進入類Desc 的 __get__()方法,進行相應的操作

 

問題2. 從上面 代碼1 我們看到了,描述符的對象 x 其實是類 TestDesc  的類屬性,那么可不可以把它變成實例屬性呢? 

    答:看看解釋器怎么說的。

#代碼 2

class Desc(object):
    def __init__(self, name):
        self.name = name
    
    def __get__(self, instance, owner):
        print("__get__...")
        print('name = ',self.name) 
        print('='*40, "\n")

class TestDesc(object):
    x = Desc('x')
    def __init__(self):
        self.y = Desc('y')      #把描述符(Desc類)的對象變成實例屬性 #以下為測試代碼
t = TestDesc()
t.x
t.y

#以下為輸出結果:
__get__...
name =  x
========================================
<__main__.Desc object at 0x03FB0088>      #沒有打印t.y的信息,但至少證明了y是Desc類的實例對象

  為什么沒有打印t.y的信息呢?

  因為沒有訪問 __get__() 方法啊,那么為啥沒有訪問 __get__() 方法呢?

  因為訪問 t.y 時刻,首先會調用TestDesc(即Owner)的 __getattribute__() 方法(就是 TestDesc.__getattribute__())  ,先來訪問實例屬性,找到 y ,又發現屬性y 為一個描述符,於是將t.y

  轉化成TestDesc.__dict__['y'].__get__(t, TestDesc)  ;但是,實際上 TestDesc 並沒有 y這個屬性,y 是屬於實例對象的,所以,只能忽略了。

 

  》》訪問實例層次上的描述符 x,只會返回描述符本身。為了讓描述符能夠正常工作,它們必須定義在類的層次上。如果不這么做,那么 Python 無法自動為你調用 __get__ 和 __set__ 方法

 

 問題3. 如果   類屬性的描述符對象  和   實例屬性描述符的對象  同名時,咋整?

#代碼 3

class Desc(object):
    def __init__(self, name):
        self.name = name
        print("__init__(): name = ",self.name)
        
    def __get__(self, instance, owner):
        print("__get__() ...")
        return self.name

    def __set__(self, instance, value):
        self.value = value
        
class TestDesc(object):
    _x = Desc('x')
    def __init__(self, x):
        self._x = x


#以下為測試代碼
t = TestDesc(10)
t._x

#輸入結果
__init__(): name =  x    #此語句什么也不輸入也會打印! 原因 TestDesc類中有一個Desc類實例化並初始化的動作,調用了__init__ __get__() ...
'x'

  根據對象屬性的訪問順序,t._x 應該先去調用 __getattribute__() 方法,然后找到了 實例t 的 _x 屬性就結束了,為啥還去調用了描述符的 __get__() 方法呢???

  這就牽扯到了一個查找順序問題當Python解釋器發現實例對象的字典中,有與  描述符同名的屬性時, 描述符優先,會覆蓋掉實例屬性

  驗證一下:用__dict__查看一下屬性

>>> t.__dict__
{}

>>> TestDesc.__dict__
mappingproxy({'__module__': '__main__', '_x': <__main__.Desc object at 0x0000000002B0BA20>, '__init__': <function TestDesc.__init__ at 0x0000000002BC59D8>, '__dict__': <attribute '__dict__' of 'TestDesc' objects>, '__weakref__': <attribute '__weakref__' of 'TestDesc' objects>, '__doc__': None})

  

  我們再將 代碼3 改進一下, 刪除 __set__() 方法試試看會發生什么情況?

#代碼 4

class Desc(object):
    def __init__(self, name):
        self.name = name
        print("__init__(): name = ",self.name)
        
    def __get__(self, instance, owner):
        print("__get__() ...")
        return self.name
        
class TestDesc(object): _x = Desc('x') def __init__(self, x): self._x = x #以下為測試代碼 t = TestDesc(10) t._x #以下為輸出: __init__(): name = x    

  !? 怎么沒有調用__get__方法?

  其實,還是 屬性查找優先級 惹的禍,只是定義一個 __get__() 方法,為非數據描述符,優先級低於實例屬性的!!

 

問題4. 什么是數據描述符,什么是非數據描述符? 

  答:一個類,如果只定義了 __get__() 方法,而沒有定義 __set__(), __delete__() 方法,則認為是非數據描述符; 反之,則成為數據描述符

 

問題5. 屬性查詢優先級

  

    ① __getattribute__(), 無條件調用

    ② 數據描述符:由 ① 觸發調用 (若人為的重載了該 __getattribute__() 方法,可能會調職無法調用描述符)

    ③ 實例對象的字典(若與描述符對象同名,會被覆蓋哦)

    ④ 類的字典

    ⑤ 非數據描述符

    ⑥ 父類的字典

    ⑦ __getattr__() 方法

 

 

參考原文  https://www.cnblogs.com/Jimmy1988/p/6808237.html  

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM