首頁 >後端開發 >Python教學 >Python 描述符(Descriptor)入門

Python 描述符(Descriptor)入門

黄舟
黄舟原創
2016-12-15 09:13:571109瀏覽

很久都沒寫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
  
#----------------------------------------------------------------------
 @property
deffull_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 的實現機制大家清楚麼? 啊。 。 。開個玩笑,我們看下面一段程式碼

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 = fdel
ifdocisNoneandfgetisnotNone:
 doc = fget.__doc__
 self.__doc__ = doc
  
def__get__(self, obj, objtype=None):
ifobjisNone:
returnself
ifself.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
  
#----------------------------------------------------------------------
 @property
deffull_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
deffull_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):
pass
if__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 。好了我們現在來看個範例:

importmath
classlazyproperty:
def__init__(self, func):
 self.func = func
  
def__get__(self, instance, owner):
ifinstanceisNone:
returnself
else:
 value = self.func(instance)
 setattr(instance, self.func.__name__, value)
returnvalue
classCircle:
def__init__(self, radius):
 self.radius = radius
pass
  
 @lazyproperty
defarea(self):
 print("Com")
returnmath.pi * self.radius *2
  
deftest(self):
pass
if__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:
returnself
else:
 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 = radius
pass
  
 @lazyproperty
defarea(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 = fdel
ifdocisNoneandfgetisnotNone:
 doc = fget.__doc__
 self.__doc__ = doc
  
def__get__(self, obj, objtype=None):
ifobjisNone:
returnself
ifself.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
  
 @Property
defValue(self):
returnself.value
  
 @Value.setter
deftest(self, x):
 self.value = x

   

如上面的例子所描述的一样,我们可以判断所传入的值是否有效等等。

以上就是Python 描述符(Descriptor)入门,更多相关文章请关注PHP中文网(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn