Python學習筆記——描述符

來源:互聯網
上載者:User

在Python中,訪問一個屬性的優先順序順序按照如下順序:

  1. 類屬性
  2. 資料描述符
  3. 執行個體屬性
  4. 非資料描述符
  5. __getattr__()方法 這個方法的完整定義如下所示:
     def __getattr(self,attr) :#attr是self的一個屬性名稱     pass;

先來闡述下什麼叫資料描述符。

資料描述符是指實現了__get__,__set__,__del__方法的類屬性(由於Python中,一切皆是對象,所以你不妨把所有的屬性也看成是對象)

PS:個人覺得這裡最好把資料描述符等效於定義了__get__,__set__,__del__三個方法的介面。

闡述下這三個方法:

__get__的標準定義是__get__(self,obj,type=None),它非常接近於JavaBean的get

第一個函數是調用它的執行個體,obj是指去訪問屬性所在的方法,最後一個type是一個選擇性參數,通常為None(這個有待於進一步的研究)

例如給定類X和執行個體x,調用x.foo,等效於調用:

       type(x).__dict__["foo"].__get__(x,type(x))

調用X.foo,等效於調用:

     type(x).__dict__["foo"].__get__(None,type(x))

第二個函數__set__的標準定義是__set__(self,obj,val),它非常接近於JavaBean的set方法,其中最後一個參數是要賦予的值

第三個函數__del__的標準定義是__del__(self,obj),它非常接近Java中Object的Finailize()方法,指Python在回收這個垃圾對象時所調用到的解構函式,只是這個函數永遠不會拋出異常。因為這個對象已經沒有引用指向它,拋出異常沒有任何意義。

接下來,我們來一一比較這些優先順序.

首先來看類屬性

     class A(object):    foo=1.3;    print str(A.__dict__);

輸出:

     {"__dict__": <attribute "__dict__" of "A" objects>, "__module__": "__main__", "foo": 1.3, "__weakref__":  <attribute "__weakref__" of "A" objects>, "__doc__": None}

從可以看出foo屬性在類的__dict__屬性裡,所以這裡用A.foo可以直接找到。這裡我們先跨過資料描述符,直接來看執行個體屬性。

     class A(object):    foo=1.3;a=A();print a.foo;a.foo=15;print a.foo;  

這裡a.foo先輸出1.3後輸出15,不是說類屬性的優先順序比執行個體屬性的優先順序高嗎?按理a.foo應該不變才對?其實,這裡只是一個假象,真正的原因在於這裡將a.foo這個引用對象,不妨將其理解為可以指向任意資料類型的指標,指向了15這個int對象。

不信,可以繼續看:

   class A(object):    foo=1.3;a=A();print a.foo;a.foo=15;print a.foo;del a.foo;print a.foo;  

這次在輸出1.3,15後最後一次又一次的輸出了1.3,原因在於a.foo最後一次又按照優先順序順序直接找到了類屬性A.foo

然後我們來看下資料描述符這一全新的語言概念。按照之前的定義,一個實現了__get__,__set__,__del__的類都統稱為資料描述符。我們來看下一個簡單的例子。

      class simpleDescriptor(object):   def __get__(self,obj,type=None) :       pass;   def __set__(self,obj,val):       pass;   def __del__(self,obj):       pass     class A(object):    foo=simpleDescriptor();print str(A.__dict__);print A.foo;a=A();print a.foo;a.foo=13;print a.foo;

這裡get,set,del方法體內容都略過,雖然簡單,但也不失為一個資料描述符。讓我們來看下它的輸出:

    {"__dict__":  <attribute "__dict__" of "A" objects >, "__module__": "__main__", "foo":  <__main__.simpleDescriptor object at 0x00C46930 >, "__weakref__":  <attribute "__weakref__" of "A" objects >, "__doc__": None}NoneNoneNone

從可以看出,儘管我們對a.foo賦值了,但其依然為None,原因就在於__get__方法什麼都不返回。

為了更進一步的加深對資料描述符的理解,我們簡單的作下改造。

   class simpleDescriptor(object):    def __init__(self):        self.result=None;    def __get__(self,obj,type=None) :        return self.result-10;    def __set__(self,obj,val):        self.result=val+3;        print self.result;    def __del__(self,obj):        pass     class A(object):    foo=simpleDescriptor();a=A();a.foo=13;print a.foo;

列印的輸出結果為:

       16    6

第一個16為我們在對a.foo賦值的時候,人為的將13加上3後作為foo的值,第二個6是我們在返回a.foo之前人為的將它減去了10。

所以我們可以猜測,常規的Python類在定義get,set方法的時候,如果無特殊需求,直接給對應的屬性賦值或直接返回該屬性值。如果自己定義類,並且繼承object類的話,這幾個方法都不用定義。

下面我們來看下執行個體屬性和非資料描述符。

     class B(object):    foo=1.3;b=B();print b.__dict__#print b.bar;b.bar=13;print b.__dict__print b.bar;

輸出結果為:
{}
{"bar": 13}
13

可見這裡在執行個體b.__dict__裡找到了bar屬性,所以這次可以擷取13了。

那麼什麼是非資料描述符呢?簡單的說,就是沒有實現get,set,del三個方法的所有類

讓我們任意看一個函數的描述:

     def hello():    passprint dir(hello)

輸出:

     ["__call__", "__class__", "__delattr__", "__dict__", "__doc__", "__get__", "__getattribute__", "__hash__", "__init__", "__module__", "__name__", "__new__", "__reduce__", "__reduce_ex__", "__repr__", "__setattr__", "__str__", "func_closure", "func_code", "func_defaults", "func_dict", "func_doc", "func_globals", "func_name"]

從上面可以看出所有的函數都有get方法,但都沒有set和del方法,所以所有的類成員函數都是非資料描述符。

看一個簡單的例子:

     class simpleDescriptor(object):        def __get__(self,obj,type=None) :        return "get",self,obj,type;class D(object):    foo=simpleDescriptor();d=D();print d.foo;d.foo=15;print d.foo;

輸出:

     ("get",  <__main__.simpleDescriptor object at 0x00C46870 >,  <__main__.D object at 0x00C46890 >,  <class "__main__.D" >)15

可以看出執行個體屬性掩蓋了非資料描述符。

最後看下__getatrr__方法。它的標準定義是:__getattr__(self,attr),其中attr是屬性名稱

讓我們來看一個簡單的例子:

     class D(object):    def __getattr__(self,attr):        return attr;        #return self.attr;        d=D();print d.foo,type(d.foo);d.foo=15;print d.foo;

輸出:

        foo <type "str" >   15

可以看的出來Python在實在找不到方法的時候,就會求助於__getattr__方法。這有點像javascript中FF的私人實現__noSuchMethod__,或ruby中的method_missing.

注意這裡要避免無意識的遞迴,稍微改動下:

       class D(object):    def __getattr__(self,attr):        #return attr;        return self.attr;        d=D();print d.foo,type(d.foo);d.foo=15;print d.foo;

這次會直接拋出堆疊溢位的異常,就像下面這樣:

      RuntimeError: maximum recursion depth exceeded
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.