Python 描述器(descriptor).docx
Python描述器(descriptor).docx目录1 .概述i2 .定义13 .问题24 .DescriptorProtocol75 .调用描述器96 .属性Properties107 .函数与方法FunctionsandMethods138 .静态方法与类方法159 .参考文章20环境macosx,python3.51 .概述定义描述器(descriptor),总结协议,展示描述器的调用,研究一个自定义的描述器,以及内置的python描述器,包括:函数,属性(properties),静态方法(staticmethods),以及类方法。通过给出纯python等效的以及一个样本application.学习有关描述器不仅仅提供通往一个更大的工具箱,它也创造了一个关于python怎么的工作的更深的理解,以及赞赏它的优雅设计。2 .定义一个descriptor是一个简单的方法管理访问属性。方式有三种:set,get,deleteo一般,一个descriptor是一个"绑定行为的对象的属性,该属性可以被在描述器协议(descriptorprotocol)中的方法重写。这些方法:get_(),_set_(),delete_(),如果任何这些方法在一个对象中被定义,就说他是个描述器。默认的访问这些属性的方法是get,set,或者delete这些属性从一个对象的字典中。例如:a.x有一个查找链,以开始,然后type,_dict_"x"L然后继续以类似的通过type基类的方式,除了元类(metaclasses),如果一个查找值是一个定义了一个描述器方法的对象,python将会重写默认的行为,并且调用描述器方法。这种情况的发生的优先链取决于哪一个描述器方法被定义了。描述器是一个强大的,通用功能的协议。他们是属性(properties),方法,静态方法,类方法,以及super。等背后的工作机理。他们被用于整个python中,来完成新实的类。3 .问题假设通过一个python写的管理系统来运营一家书店,系统中包含一个类Book,采集作者,标题,书的价格。classBook(object):def_init_(self,author,title,price):self.author=authorself.title=titleself.price=pricedef_str_(self):return"0-l".format(self.author,self.title)12345678从上面的定义来看,这个设计是没什么问题,但是可以发现,书的价格可以是任意值,包括负值,这与实际情况不合作如下修改:fromweakrefimportWeakKeyDictionaryclassPrice(object):def_init_(self):self.default=0self.values=WeakKeyDictionary()def_get_(self,instance):returnself.values.get(instance,self.default)def_set_(self,instance,value):ifvalue<0orvalue>100:raiseValueError("Pricemustbebetween0and100.")self.valuesinstance=valuedef_delete_(self,instance):delself.valuesinstance123456789101112131415161718注:使用弱引用weakref,使得不在使用的对象能被垃圾回收。修改Book类:classBook(object):price=Price()def_init_(self,author,title,price):self.author=authorself.title=titleself.price=pricedef_str_(self):return"0-l".format(self.author,self.title)12345678910b=Book("WilliamFaulkner","TheSoundandtheFury",12)b.price#12b.price=-12#Traceback(mostrecentcalllast):#File"<pyshell#68>"zline1,in<module>#b.price=-12#File"<pyshell#58>"/line9,in_set_#raiseValueError("Pricemustbebetween0and100.")#ValueError:Pricemustbebetween0and100.b.price=101#Traceback(mostrecentcalllast):#File"<pyshell#69>"/line1,in<module>#b.price=101#File"<pyshell#58>"/line9,in_set_#raiseValueError("Pricemustbebetween0and100.")#ValueError:Pricemustbebetween0and100.123456789101213141516171819当获取b.prce的值时,python识别出price是个描述器,并且调用Book.price._get_。当更改price的值时,如b.price=60,python再次识别price是个描述器,使用Book.price._set_替代赋值。当删除Book实例的price属性时,python自动调用Book.price.delete_以上的Price定义中使用了弱引用,如果不使用:classPrice(object):def_init_(self):self._price=0第5页共20页def_get_(self,instance):returnself._pricedef_set_(self,instance,value):ifvalue<0orvalue>100:raiseValueError("Pricemustbebetween0and100.")self._price=valuedef_delete_(self,instance):delself._price123456789101112131415结果:bl=Book("WilliamFaulkner","TheSoundandtheFury",12)bl.price#12b2=Book("JohnDosPassos","ManhattanTransfer",13)bl.price#131第#页共20页234567按照定义中的第二部分说的。查找属性的顺序:b.diet#'title*:'thesoundandthefury','author':'william'12没有price这一属性。type(b)._dict_#mappingproxy('_init_<functionBook._init_at0xl06830840>,'_doc_None,'_diet_<attribute'_diet_1of'Book'objects>,'_str_<functionBook,strat0xl068306a8>,1module'main1weakref<attribute'_weakref_'of'Book'objects>,'price':<_main_.Priceobjectat0xl066fe048>)#ortype(b)._diet_"price"#<_main_.Priceobjectat0xl066fe048>123456可以知道,在给b2赋值price时,实际上修改的是price这个对象的值,同时,依据元类的原理(metaclass),Price也是类,所以bl的属性price也跟着变化。4.DescriptorProtocol第7页共20页descr._get_(self,obj,type=None)->valuedescr._set_(self,obj,value)->Nonedescr._delete_(self,obj)->None如果一个对象定义类get_()以及_set_(),那么它被视为一个资料描述器(datadescriprot)o仅仅定义_get_()被称为非资料描述器(non-datadescriprot).资料描述器和非资料描述器的区别在于:相对于实例的字典的优先级。如果实例字典中有与描述器同名的属性,如果描述器是资料描述器,优先使用资料描述器,如果是非资料描述器,优先使用字典中的属性。(实例a的方法和属性重名时,比如都叫fooPython会在访问a.foo的时候优先访问实例字典中的属性,因为实例函数的实现是个非资料描述器)classtest:def_init_(self):self.method=99defmethod(self):print("output.")t=test()t.method#99t.method()#Traceback(mostrecentcalllast):#File"<stdin>"/line1,in<module>#TypeError:'int'objectisnotcallablet._diet_#'method':99123456789第9页共20页10111213141516查字典的方式使人想到元类中使用type进行的动态定义类的最后一个参数的定义。如果要定义一个只读的资料描述器,定义get_()以及_set_()并设置_set_()引起AttributeError的调用。5.调用描述器一个描述器可以直接使用方法的名字直接调用。d.get(obj)o或者,通过访问属性的方式是更常见的自动调用描述器的方式.例如obj.d在obj的字典中查找do如果定义了get(),然后d.get(obj)会根据优先规则列表被调用。对于对象:运行机制在object.getattribute。里面,它将b.x转换成type(b).dietget(b,type(b).工作的完成是通过优先级链,给予datadescriptor高于实例变量的优先级,或者,实例变量高于non-datadescriptor的优先级,并分配最低的优先级给getattr_()(如果存在)。整个过程的是现在Objects/object.c中的PyObject_GenericGetAttr()o对于类:运行的机制在type.getattribute(),它将B.x转换成B