很久都沒寫 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 的實現機制大家清楚麼?什麼不清楚?那還學個毛的 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 = 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 tomethods, 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
如上面的例子所描述的一样,我们可以判断所传入的值是否有效等等。