Python 描述符(Descriptor)入门
很久都没写Flask代码相关了,想想也真是惭愧,然并卵,这次还是不写Flask相关,不服你来打我啊(就这么贱,有本事咬我啊
这次我来写一下Python一个很重要的东西,即Descriptor(描述符)
初识描述符
老规矩,Talkischeap,Showmethecode.我们先来看看一段代码
classPerson(object): """""" #---------------------------------------------------------------------- def__init__(self,first_name,last_name): """Constructor""" self.first_name=first_name self.last_name=last_name #---------------------------------------------------------------------- @property deffull_name(self): """ Returnthefullname """ return"%s%s"%(self.first_name,self.last_name) if__name__=="__main__": person=Person("Mike","Driscoll") print(person.full_name) #'MikeDriscoll' print(person.first_name) #'Mike'
这段代大家肯定很熟悉,恩,property嘛,谁不知道呢,但是property的实现机制大家清楚么?什么不清楚?那还学个毛的Python啊。。。开个玩笑,我们看下面一段代码
classProperty(object): "EmulatePyProperty_Type()inObjects/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("unreadableattribute") returnself.fget(obj) def__set__(self,obj,value): ifself.fsetisNone: raiseAttributeError("can'tsetattribute") self.fset(obj,value) def__delete__(self,obj): ifself.fdelisNone: raiseAttributeError("can'tdeleteattribute") 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): """ Returnthefullname """ return"%s%s"%(self.first_name,self.last_name) if__name__=="__main__": person=Person("Mike","Driscoll") print(person.full_name) #'MikeDriscoll' print(person.first_name) #'Mike'
首先,如果你对装饰器不了解的话,你可能要去看看这篇文章,简而言之,在我们正式运行代码之前,我们的解释器就会对我们的代码进行一次扫描,对涉及装饰器的部分进行替换。类装饰器同理。在上文中,这段代码
@Property deffull_name(self): """ Returnthefullname """ return"%s%s"%(self.first_name,self.last_name)
会触发这样一个过程,即full_name=Property(full_name)。然后在我们后面所实例化对象之后我们调用person.full_name这样一个过程其实等价于person.full_name.__get__(person)然后进而触发__get__()方法里所写的returnself.fget(obj)即原本上我们所编写的deffull_name内的执行代码。
这个时候,同志们可以去思考下getter(),setter(),以及deleter()的具体运行机制了=。=如果还是有问题,欢迎在评论里进行讨论。
关于描述符
还记得之前我们所提到的一个定义么:Descriptors是一种特殊的对象,这种对象实现了__get__,__set__,__delete__这三个特殊方法。然后在Python官方文档的说明中,为了体现描述符的重要性,有这样一段话:“Theyarethemechanismbehindproperties,methods,staticmethods,classmethods,andsuper().TheyareusedthroughoutPythonitselftoimplementthenewstyleclassesintroducedinversion2.2.”简而言之就是先有描述符后有天,秒天秒地秒空气。恩,在新式类中,属性,方法调用,静态方法,类方法等都是基于描述符的特定使用。
OK,你可能想问,为什么描述符是这么重要呢?别急,我们接着看
使用描述符
首先请看下一段代码
classA(object):#注:在Python3.x版本中,对于newclass的使用不需要显式的指定从object类进行继承,如果在Python2.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))。好的这里我们又要给出一个结论了:“在这样一个调用过程中,有这样一个优先级顺序,如果我们所尝试调用属性是一个datadescriptors,那么不管这个属性是否存在我们的实例的__dict__字典中,优先调用我们描述符里的__get__方法,如果我们所尝试调用属性是一个nondatadescriptors,那么我们优先调用我们实例里的__dict__里的存在的属性,如果不存在,则依照相应原则往上查找我们类,父类中的__dict__中所包含的属性,一旦属性存在,则调用__get__方法,如果不存在则调用__getattr__()方法”。理解起来有点抽象?没事,我们马上会讲,不过在这里,我们先要解释下datadescriptors与nondatadescriptors,再来看一个例子。什么是datadescriptors与nondatadescriptors呢?其实很简单,在描述符中同时实现了__get__与__set__协议的描述符是datadescriptors,如果只实现了__get__协议的则是nondatadescriptors。好了我们现在来看个例子:
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的属性,同时这是一个nondatadescriptors,由于我们的实例字典内并不存在area属性,那么我们便调用类字典中的area的__get__方法,并在__get__方法中通过调用setattr方法为实例字典注册属性area。紧接着,我们在后续调用c.area的时候,我们能在实例字典中找到area属性的存在,且类字典中的area是一个nondatadescriptors,于是我们不会触发代码里所实现的__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("Somethingwentwring") returnmath.pi*value*2ifvalue!=0elsemath.pi*self.radius*2 deftest(self): pass
利用描述符的特性实现懒加载,再比如,我们可以控制属性赋值的值
classProperty(object): "EmulatePyProperty_Type()inObjects/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("unreadableattribute") returnself.fget(obj) def__set__(self,obj,value=None): ifvalueisNone: raiseTypeError("Youcan`ttosetvalueasNone") ifself.fsetisNone: raiseAttributeError("can'tsetattribute") self.fset(obj,value) def__delete__(self,obj): ifself.fdelisNone: raiseAttributeError("can'tdeleteattribute") 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
如上面的例子所描述的一样,我们可以判断所传入的值是否有效等等。