python屬性查找 深刻理解(attribute lookup)

在Python中,屬性查找(attribute lookup)是比較複雜的,特別是涉及到描述符descriptor的時候。html

   

  在上一文章末尾,給出了一段代碼,就涉及到descriptor與attribute lookup的問題。而get系列函數(__get__, __getattr__, __getattribute__) 也很容易搞暈,本文就這些問題簡單總結一下。python

  首先,咱們知道:緩存

  •     python中一切都是對象,「everything is object」,包括類,類的實例,數字,模塊app

  •     任何object都是類(class or type)的實例(instance)ide

  •     若是一個descriptor只實現了__get__方法,咱們稱之爲non-data descriptor, 若是同時實現了__get__ __set__咱們稱之爲data descriptor。函數

 

    按照python doc,若是obj是某個類的實例,那麼obj.name首先調用__getattribute__。若是類定義了__getattr__方法,那麼在__getattribute__拋出 AttributeError 的時候就會調用到__getattr__,而對於描述符(__get__)的調用,則是發生在__getattribute__內部的。官網文檔是這麼描述的測試

    The implementation works through a precedence chain that gives data descriptors priority over instance variables, instance variables priority over non-data descriptors, and assigns lowest priority to __getattr__() if provided.spa

    obj = Clz(), 那麼obj.attr 順序以下:code

    (1)若是「attr」是出如今Clz或其基類的__dict__中, 且attr是data descriptor, 那麼調用其__get__方法, 不然orm

    (2)若是「attr」出如今obj的__dict__中, 那麼直接返回 obj.__dict__['attr'], 不然

    (3)若是「attr」出如今Clz或其基類的__dict__中

        (3.1)若是attr是non-data descriptor,那麼調用其__get__方法, 不然

        (3.2)返回 __dict__['attr']

    (4)若是Clz有__getattr__方法,調用__getattr__方法,不然

    (5)拋出AttributeError 

  下面是測試代碼:

  

 View Code

 

  注意第50行,change_attr給實例的__dict__裏面增長了兩個屬性。經過上下兩條print的輸出以下:

  Derive object dict {'same_name_attr': 'attr in object', 'not_des_attr': 'I am not descriptor attr'}

  Derive object dict {'same_name_attr': 'attr in object', 'ndd_derive': 'ndd_derive now in object dict ', 'not_des_attr': 'I am not descriptor attr', 'dd_base': 'dd_base now in object dict '}

 

  調用change_attr方法以後,dd_base既出如今類的__dict__(做爲data descriptor), 也出如今實例的__dict__, 新航道雅思培訓由於attribute lookup的循序,因此優先返回的仍是Clz.__dict__['dd_base']。而ndd_base雖然出如今類的__dict__, 可是由於是nondata descriptor,因此優先返回obj.__dict__['dd_base']。其餘:line48,line56代表了__getattr__的做用。line49代表obj.__dict__優先於Clz.__dict__

 

  前面提到過,類的也是對象,類是元類(metaclass)的實例,因此類屬性的查找順序基本同上,區別在於第二步,因爲Clz可能有基類,因此是在Clz及其基類的__dict__查找「attr"

  

  文末,咱們再來看一下這段代碼。

   

複製代碼

 1 import functools, time 2 class cached_property(object): 3     """ A property that is only computed once per instance and then replaces 4         itself with an ordinary attribute. Deleting the attribute resets the 5         property. """ 6  7     def __init__(self, func): 8         functools.update_wrapper(self, func) 9         self.func = func10 11     def __get__(self, obj, cls):12         if obj is None: return self13         value = obj.__dict__[self.func.__name__] = self.func(obj)14         return value15 16 class TestClz(object):17     @cached_property18     def complex_calc(self):19         print 'very complex_calc'20         return sum(range(100))21 22 if __name__=='__main__':23     t = TestClz()24     print '>>> first call'25     print t.complex_calc26     print '>>> second call'27     print t.complex_calc

複製代碼

 

    cached_property是一個non-data descriptor。在TestClz中,用cached_property裝飾方法complex_calc,返回值是一個descriptor實例,因此在調用的時候沒有使用小括號。

    第一次調用t.complex_calc以前,obj(t)的__dict__中沒有」complex_calc「, 根據查找順序第三條,執行cached_property.__get__, 這個函數代用緩存的complex_calc函數計算出結果,而且把結果放入obj.__dict__。那麼第二次訪問t.complex_calc的時候,根據查找順序,第二條有限於第三條,因此就直接返回obj.__dict__['complex_calc']。bottle的源碼中還有兩個descriptor,很是厲害!

相關文章
相關標籤/搜索