Python 描述符(Descriptor)入門

來源:互聯網
上載者:User
很久都沒寫 Flask 代碼相關了,想想也真是慚愧,然並卵,這次還是不寫 Flask 相關,不服你來打我啊(就這麼賤,有本事咬我啊

這次我來寫一下 Python 一個很重要的東西,即 Descriptor (描述符)

初識描述符

老規矩, Talk is cheap,Show me the code. 我們先來看看一段代碼

classPerson(object):""""""  #----------------------------------------------------------------------def__init__(self, first_name, last_name):"""Constructor""" self.first_name = first_name self.last_name = last_name  #---------------------------------------------------------------------- @propertydeffull_name(self):""" Return the full name """return"%s %s"% (self.first_name, self.last_name)  if__name__=="__main__": person = Person("Mike","Driscoll") print(person.full_name)# 'Mike Driscoll' print(person.first_name)# 'Mike'


這段代大家肯定很熟悉,恩, property 嘛,誰不知道呢,但是 property 的實現機制大家清楚嗎?什麼不清楚?那還學個毛的 Python 啊。。。開個玩笑,我們看下面一段代碼

classProperty(object):"Emulate PyProperty_Type() in Objects/descrobject.c"def__init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdelifdocisNoneandfgetisnotNone: doc = fget.__doc__ self.__doc__ = doc  def__get__(self, obj, objtype=None):ifobjisNone:returnselfifself.fgetisNone:raiseAttributeError("unreadable attribute")returnself.fget(obj)  def__set__(self, obj, value):ifself.fsetisNone:raiseAttributeError("can't set attribute") self.fset(obj, value)  def__delete__(self, obj):ifself.fdelisNone:raiseAttributeError("can't delete attribute") self.fdel(obj)  defgetter(self, fget):returntype(self)(fget, self.fset, self.fdel, self.__doc__)  defsetter(self, fset):returntype(self)(self.fget, fset, self.fdel, self.__doc__)  defdeleter(self, fdel):returntype(self)(self.fget, self.fset, fdel, self.__doc__)

看起來是不是很複雜,沒事,我們來一步步的看。不過這裡我們首先給出一個結論: Descriptors 是一種特殊 的對象,這種對象實現了 __get__ , __set__ , __delete__ 這三個特殊方法。

詳解描述符

說說 Property

在上文,我們給出了 Propery 實現代碼,現在讓我們來詳細說說這個

classPerson(object):""""""  #----------------------------------------------------------------------def__init__(self, first_name, last_name):"""Constructor""" self.first_name = first_name self.last_name = last_name  #---------------------------------------------------------------------- @Propertydeffull_name(self):""" Return the full name """return"%s %s"% (self.first_name, self.last_name)  if__name__=="__main__": person = Person("Mike","Driscoll") print(person.full_name)# 'Mike Driscoll' print(person.first_name)# 'Mike'

首先,如果你對裝飾器不瞭解的話,你可能要去看看這篇文章,簡而言之,在我們正式運行代碼之前,我們的解譯器就會對我們的代碼進行一次掃描,對涉及裝飾器的部分進行替換。類裝飾器同理。在上文中,這段代碼

@Propertydeffull_name(self): """  Return the full name  """return"%s %s"% (self.first_name, self.last_name)

會觸發這樣一個過程,即 full_name=Property(full_name) 。然後在我們後面所執行個體化對象之後我們調用 person.full_name 這樣一個過程其實等價於 person.full_name.__get__(person) 然後進而觸發 __get__() 方法裡所寫的 return self.fget(obj) 即原本上我們所編寫的 def full_name 內的執行代碼。

這個時候,同志們可以去思考下 getter() , setter() ,以及 deleter() 的具體運行機制了=。=如果還是有問題,歡迎在評論裡進行討論。

關於描述符

還記得之前我們所提到的一個定義麼: Descriptors 是一種特殊的對象,這種對象實現了 __get__ , __set__ , __delete__ 這三個特殊方法 。然後在 Python 官方文檔的說明中,為了體現描述符的重要性,有這樣一段話:“They are the mechanism behind properties, methods, static methods, class methods, and super(). They are used throughout Python itself to implement the new style classes introduced in version 2.2. ” 簡而言之就是 先有描述符後有天,秒天秒地秒空氣 。恩,在新式類中,屬性,方法調用,靜態方法,類方法等都是基於描述符的特定使用。

OK,你可能想問,為什麼描述符是這麼重要呢?別急,我們接著看

使用描述符

首先請看下一段代碼

classA(object):#註:在 Python 3.x 版本中,對於 new class 的使用不需要顯式的指定從 object 類進行繼承,如果在 Python 2.X(x>2)的版本中則需要

defa(self):passif__name__=="__main__": a=A() a.a()

大家都注意到了我們存在著這樣一個語句 a.a() ,好的,現在請大家思考下,我們在調用這個方法的時候發生了什嗎?

OK?想出來了嗎?沒有?好的我們繼續

首先我們調用一個屬性的時候,不管是成員還是方法,我們都會觸發這樣一個方法用於調用屬性 __getattribute__() ,在我們的 __getattribute__() 方法中,如果我們嘗試調用的屬性實現了我們的描述符協議,那麼會產生這樣一個調用過程 type(a).__dict__['a'].__get__(b,type(b)) 。好的這裡我們又要給出一個結論了:“在這樣一個調用過程中,有這樣一個優先順序順序,如果我們所嘗試調用屬性是一個 data descriptors ,那麼不管這個屬性是否存在我們的執行個體的 __dict__ 字典中,優先調用我們描述符裡的 __get__ 方法,如果我們所嘗試調用屬性是一個 non data descriptors ,那麼我們優先調用我們執行個體裡的 __dict__ 裡的存在的屬性,如果不存在,則依照相應原則往上尋找我們類,父類中的 __dict__ 中所包含的屬性,一旦屬性存在,則調用 __get__ 方法,如果不存在則調用 __getattr__() 方法”。理解起來有點抽象?沒事,我們馬上會講,不過在這裡,我們先要解釋下 data descriptors 與 non data descriptors ,再來看一個例子。什麼是 data descriptors 與 non data descriptors 呢?其實很簡單,在描述符中同時實現了 __get__ 與 __set__ 協議的描述符是 data descriptors ,如果只實現了 __get__ 協議的則是 non data descriptors 。好了我們現在來看個例子:

importmathclasslazyproperty:def__init__(self, func): self.func = func  def__get__(self, instance, owner):ifinstanceisNone:returnselfelse: value = self.func(instance) setattr(instance, self.func.__name__, value)returnvalueclassCircle:def__init__(self, radius): self.radius = radiuspass   @lazypropertydefarea(self): print("Com")returnmath.pi * self.radius *2  deftest(self):passif__name__=='__main__': c=Circle(4) print(c.area)

好的,讓我們仔細來看看這段代碼,首先類描述符 @lazyproperty 的替換過程,前面已經說了,我們不在重複。接著,在我們第一次調用 c.area 的時候,我們首先查詢執行個體 c 的 __dict__ 中是否存在著 area 描述符,然後發現在 c 中既不存在描述符,也不存在這樣一個屬性,接著我們向上查詢 Circle 中的 __dict__ ,然後尋找到名為 area 的屬性,同時這是一個 non data descriptors ,由於我們的執行個體字典內並不存在 area 屬性,那麼我們便調用類字典中的 area 的 __get__ 方法,並在 __get__ 方法中通過調用 setattr 方法為執行個體字典註冊屬性 area 。緊接著,我們在後續調用 c.area 的時候,我們能在執行個體字典中找到 area 屬性的存在,且類字典中的 area 是一個 non data descriptors ,於是我們不會觸發代碼裡所實現的 __get__ 方法,而是直接從執行個體的字典中直接擷取屬性值。

描述符的使用

描述符的使用面很廣,不過其主要的目的在於讓我們的調用過程變得可控。因此我們在一些需要對我們調用過程實行精細控制的時候,使用描述符,比如我們之前提到的這個例子

classlazyproperty:def__init__(self, func): self.func = func  def__get__(self, instance, owner):ifinstanceisNone:returnselfelse: value = self.func(instance) setattr(instance, self.func.__name__, value)returnvalue  def__set__(self, instance, value=0):pass    importmath    classCircle:def__init__(self, radius): self.radius = radiuspass   @lazypropertydefarea(self, value=0): print("Com")ifvalue ==0andself.radius ==0:raiseTypeError("Something went wring")  returnmath.pi * value *2ifvalue !=0elsemath.pi * self.radius *2  deftest(self):pass

利用描述符的特性實現懶載入,再比如,我們可以控制屬性賦值的值

classProperty(object):"Emulate PyProperty_Type() in Objects/descrobject.c"def__init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdelifdocisNoneandfgetisnotNone: doc = fget.__doc__ self.__doc__ = doc  def__get__(self, obj, objtype=None):ifobjisNone:returnselfifself.fgetisNone:raiseAttributeError("unreadable attribute")returnself.fget(obj)  def__set__(self, obj, value=None):ifvalueisNone:raiseTypeError("You can`t to set value as None")ifself.fsetisNone:raiseAttributeError("can't set attribute") self.fset(obj, value)  def__delete__(self, obj):ifself.fdelisNone:raiseAttributeError("can't delete attribute") self.fdel(obj)  defgetter(self, fget):returntype(self)(fget, self.fset, self.fdel, self.__doc__)  defsetter(self, fset):returntype(self)(self.fget, fset, self.fdel, self.__doc__)  defdeleter(self, fdel):returntype(self)(self.fget, self.fset, fdel, self.__doc__)  classtest():def__init__(self, value): self.value = value   @PropertydefValue(self):returnself.value   @Value.setterdeftest(self, x): self.value = x

如上面的例子所描述的一樣,我們可以判斷所傳入的值是否有效等等。

以上就是Python 描述符(Descriptor)入門,更多相關文章請關注topic.alibabacloud.com(www.php.cn)!

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.