Python描述符 (descriptor) 詳解 ,轉於https://www.cnblogs.com/Jimmy1988/p/6808237.html

一、什麼是描述符?python

   python描述符是一個「綁定行爲」的對象屬性,在描述符協議中,它能夠經過方法重寫屬性的訪問。這些方法有 __get__(), __set__(), 和__delete__()。若是這些方法中的任何一個被定義在一個對象中,這個對象就是一個描述符。app

  以上爲官方定義,純粹爲了裝逼使用,通常人看這些定義都有一種問候祖先的衝動!測試

  不要緊,看完本文,你就會理解什麼叫描述符了!spa

二、講解描述符前,先看一下屬性:__dict__ (每一個對象均具有該屬性)code

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

對象屬性的訪問順序:blog

①.實例屬性get

②.類屬性原型

③.父類屬性it

④.__getattr__()方法

以上順序,切記切記!

複製代碼
 1 class Test(object):
 2     cls_val = 1
 3     def __init__(self):
 4         self.ins_val = 10
 5 
 6         
 7 >>> t=Test()
 8 >>> Test.__dict__
 9 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})
10 >>> t.__dict__
11 {'ins_val': 10}
12 
13 >>> type(x)==X
14 True
15 
16 #更改實例t的屬性cls_val,只是新增了該屬性,並不影響類Test的屬性cls_val
17 >>> t.cls_val = 20
18 >>> t.__dict__
19 {'ins_val': 10, 'cls_val': 20}
20 >>> Test.__dict__
21 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})
22 
23 #更改了類Test的屬性cls_val的值,因爲事先增長了實例t的cls_val屬性,所以不會改變實例的cls_val值(井水不犯河水)
24 >>> Test.cls_val = 30
25 >>> t.__dict__
26 {'ins_val': 10, 'cls_val': 20}
27 >>> Test.__dict__
28 mappingproxy({'__module__': '__main__', 'cls_val': 30, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})
複製代碼

從以上代碼能夠看出,實例t的屬性並不包含cls_val,cls_val是屬於類Test的。

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

   方法的原型爲:

  ① __get__(self, instance, owner)

  ② __set__(self, instance, value)

  ③ __del__(self, instance)

  那麼以上的 self, instance owner到底指社麼呢?莫急莫急,聽我慢慢道來!

  首先咱們先看一段代碼:

複製代碼
 1 #代碼 1
 2 
 3 class Desc(object):
 4     
 5     def __get__(self, instance, owner):
 6         print("__get__...")
 7         print("self : \t\t", self)
 8         print("instance : \t", instance)
 9         print("owner : \t", owner)
10         print('='*40, "\n")
11         
12     def __set__(self, instance, value):
13         print('__set__...')
14         print("self : \t\t", self)
15         print("instance : \t", instance)
16         print("value : \t", value)
17         print('='*40, "\n")
18 
19 
20 class TestDesc(object):
21     x = Desc()
22 
23 #如下爲測試代碼
24 t = TestDesc()
25 t.x
26 
27 #如下爲輸出信息:
28 
29 __get__...
30 self :          <__main__.Desc object at 0x0000000002B0B828>
31 instance :      <__main__.TestDesc object at 0x0000000002B0BA20>
32 owner :      <class '__main__.TestDesc'>
33 ======================================== 
複製代碼

 

能夠看到,實例化類TestDesc後,調用對象t訪問其屬性x,會自動調用類Desc的 __get__方法,由輸出信息能夠看出:

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

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

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

到此,我能夠揭開小小的謎底了,其實,Desc類就是是一個描述符(描述符是一個類哦),爲啥呢?由於類Desc定義了方法 __get__, __set__.

因此,某個類,只要是內部定義了方法 __get__, __set__, __delete__ 中的一個或多個,就能夠稱爲描述符(^_^,簡單吧)

 

說到這裏,咱們的任務還遠遠沒有完成,還存在不少不少的疑點?

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

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

      首先:訪問Owner的__getattribute__()方法(其實就是 TestDesc.__getattribute__()),訪問實例屬性,發現沒有,而後去訪問父類TestDesc,找到了!

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

      而後:進入類Desc的 __get__()方法,進行相應的操做

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

    答:我說了你不算,你說了也不算,解釋器說了算,看看解釋器怎麼說的。

複製代碼
 1 #代碼 2
 2 
 3 class Desc(object):
 4     def __init__(self, name):
 5         self.name = name
 6     
 7     def __get__(self, instance, owner):
 8         print("__get__...")
 9         print('name = ',self.name) 
10         print('='*40, "\n")
11 
12 class TestDesc(object):
13     x = Desc('x')
14     def __init__(self):
15         self.y = Desc('y')
16 
17 #如下爲測試代碼
18 t = TestDesc()
19 t.x
20 t.y
21 
22 #如下爲輸出結果:
23 __get__...
24 name =  x
25 ======================================== 
複製代碼

 

    咦,爲啥沒打印 t.y 的信息呢?

    由於沒有訪問 __get__() 方法啊,哈哈,那麼爲啥沒有訪問 __get__() 方法呢?(問題真多)

    由於調用 t.y 時刻,首先會去調用TestDesc(即Owner)的 __getattribute__() 方法,該方法將 t.y 轉化爲TestDesc.__dict__['y'].__get__(t, TestDesc), 可是呢,實際上 TestDesc 並無 y這個屬性,y 是屬於實例對象的,因此,只能忽略了。

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

    答:仍是讓解釋器來解釋一下吧。

複製代碼
 1 #代碼 3
 2 
 3 class Desc(object):
 4     def __init__(self, name):
 5         self.name = name
 6         print("__init__(): name = ",self.name)
 7         
 8     def __get__(self, instance, owner):
 9         print("__get__() ...")
10         return self.name
11 
12     def __set__(self, instance, value):
13         self.value = value
14         
15 class TestDesc(object):
16     _x = Desc('x')
17     def __init__(self, x):
18         self._x = x
19 
20 
21 #如下爲測試代碼
22 t = TestDesc(10)
23 t._x
24 
25 #輸入結果
26 __init__(): name =  x
27 __get__() ...
複製代碼

 

    不對啊,按照慣例,t._x 會去調用 __getattribute__() 方法,而後找到了 實例t 的 _x 屬性就結束了,爲啥還去調用了描述符的 __get__() 方法呢?

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

    不信?來看一下 字典 :

複製代碼
1 >>> t.__dict__
2 {}
3 
4 >>> TestDesc.__dict__
5 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__() 方法試試看會發生什麼狀況?

複製代碼
 1 #代碼 4
 2 
 3 class Desc(object):
 4     def __init__(self, name):
 5         self.name = name
 6         print("__init__(): name = ",self.name)
 7         
 8     def __get__(self, instance, owner):
 9         print("__get__() ...")
10         return self.name
11         
12 class TestDesc(object):
13     _x = Desc('x')
14     def __init__(self, x):
15         self._x = x
16 
17 
18 #如下爲測試代碼
19 t = TestDesc(10)
20 t._x
21 
22 #如下爲輸出:
23 __init__(): name =  x
複製代碼

 

    我屮艸芔茻,咋回事啊?怎麼木有去 調用 __get__() 方法?

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

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

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

  問題5. 每天提屬性查詢優先級,就不能總結一下嗎?

    答:好的好的,客官稍等!

    ① __getattribute__(), 無條件調用

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

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

    ④ 類的字典

    ⑤ 非數據描述符

    ⑥ 父類的字典

    ⑦ __getattr__() 方法

相關文章
相關標籤/搜索