《Python Descriptors 指南.docx》由会员分享,可在线阅读,更多相关《Python Descriptors 指南.docx(13页珍藏版)》请在第一文库网上搜索。
1、PythonDescriptors指南docx目录1 .约定i2 .摘要13 .定义和介绍24 .描述符协议25 .调用描述符36 .描述符举例47 .Properties68 .FunctionsandMethods99 .StaticMethodsandClassMethods11原文地址:1 .约定descriptor:描述符datadescriptor:数据描述符non-datadescriptor:非数据描述符objectattribute:对象属性(包括classinstance和classobject)attributeaccess:属性访问method:方法function:函
2、数2 .摘要文章对描述符的定义以及描述符协议做了描述,并且展示了描述符的调用方式。举例说明了一个自定义描述符以及一些Python内置的描述符,包括:functions,properties,staticmethods和classmethodso通过用乡在Python代码编码来展示它们内部实际的运行方式。学习描述符不仅仅能帮我们接触到更大型的Python工具集,也有助于我们更深入理解Python的运行机理,同时会感叹它优雅的设计理念。3 .定义和介绍一般来说,描述符就是一个被绑定了某种行为的对象属性,这个对象属性的访问方式由描述符协议中定义的方法重写了。这些方法包括:getset_和_delet
3、e_,如果某个对象定义了其中任何一个方法,这个对象就可以称之为描述符。Python中一个对象的属性访问方式默认有三种:get,set和delete,并且是通过对象的属性字典来访问。例如:a.x的查找链开始于a._dict_x,接着是type(a).dietx,接着是type(a)的基类中的属性这样继续下去,除了metaclasseso如果被查找的值是一个定义了描述符协议中提到的方法的对象,那么Python可能会重写(override)属性查找的默认行为,转而调用描述符方法。至于具体的调用过程取决于对象定义了哪种描述符方法。需要注意的是,描述符特性只适用于newstyleobjects/clas
4、ses(继承自object或者type的新式类)描述符协议是一个强大的、通用协议。Python的properties,methods,staticmethods,classmethods和super()底层的运行机制就跟描述符协议有关。描述符被广泛用于Python自身来实现新式类(Python2.2开始有的特性)。描述符简化了底层的C代码,为我们平常写的Python代码提供了一个新的、灵活的工具集。4 .描述符协议descr._get_(self,obj,type=None)valuedescr._set_(self,obj,value)-Nonedescr.delete_(self,obj)
5、-None1234以上就是它的全部方法。如果某个对象定义了其中的任何一个方法,这个对象就可以被称为描述符,当该对象被查找时就会就会重写对象调用的默认行为。如果一个对象定义了get_和_set_方法,它就是数据描述符。如果只定义了get_方法就是非数据描述符(它们通常用在方法对象上,也可以用在其它对象上)数据和非数据描述符对象被调用的方式取决于实例对象的属性字典中属性的查找方式。如果一个实例对象的属性字典中有个记录名字和数据描述符名字相同,那么数据描述符会先被查找到。如果一个实例对象的属性字典中有个记录名字和非数据描述符名字相同,那么属性字典中的记录会先被查找到。要创建一个只读的数据描述符,需要
6、定义_get_和_set_方法,并且_set_方法中要抛出AttributeError异常,这样就足够了。5 .调用描述符一个描述符能通过自身的方法直接调用,例如:d.get_(obj),不过更常用的是通过属性访问来被自动调用。例如:obj.d会在obj的字典中找d,如果d定义了那么d._get_(obj)就会被调用。具体调用流程取决于obj是object还是class,但不管是哪种,描述符只在新式类(objectsandclasses)中有效对于objects,转换机制在object.getattribute方法中,它将b.x转化成type(b).dietx.get(b,type(b)o在P
7、ython内部实现的查找链中,数据描述符的优先级比实例变量的优先级高,实例变量的优先级比非数据描述符的优先级高,而_getattr_方法的优先级最低(如果定义了这个方法的话)。具体的C代码实现在Objects/object.c的PyObject_GenericGetAttr()函数中对于classes,转化机制在type.getattribute方法中,它将B.x转换成B._diet_x.get_(None,B)o以上转换用Python代码表示如下:defgetattribute_(self,key):Emulatetype_getattro()inObjects/typeobject.cv=
8、object.getattribute_(self,key)ifhasattr(v,get_):第3页共13页returnv._get_(None,self)returnv123456有几个重要的点描述符由getattribute方法调用重写getattribute方法会阻止描述符的自动调用getattribute_方法只在新式类(classes和objects)中存在object.getattribute_和type.getattribute_调用get_的方式不同数据描述符优先级高于实例变量,实例变量优先级高于非数据描述符super()方法返回的对象也有个定制的_getattribute_
9、方法调用描述符。super(B,obj).m()调用会按照obj._class_._mro_查找B类的基类A,并且返回A.dietm.get(obj,A)。如果m不是一个描述符,m按原样返回。如果m不在A的字典里,m转为如下的查找方式:object.getattribute注意,Python2.2的时候,如果m是个数据描述符super(B,obj).m()只会调用get_方法。Python2.3的时候,如果是个非数据描述符也会被调用。以上的实现细节在源代码:Objects/typeobject.c的方法:super_getattro()中,类似的Python实现可以参考Guido,sTutor
10、ial以上所描述的描述符机制实现嵌入在object,type和super的getattribute_()方法中6 .描述符举例下面这段代码创建了一个MyClass类,它的x属性是一个描述符。通过对m对象的属性调用m.x来展示描述符的调用方式。classRevealAccess(object):,NIAdatadescriptorthatsetsandreturnsvaluesnormallyandprintsamessageloggingtheiraccess.def_init_(self,initval=None,name=,var,):self.val=initvalself.name=n
11、amedefget_(self,obj,objtype):printRetrieving,self.namereturnself.valdef_set_(self,obj,val):printUpdating1,self.nameself.val=valclassMyClass(object):x=RevealAccess(10,varx)y=5m=MyClass()m.xRetrievingvarx10m.x=20Updatingvar,x,m.xRetrievingvarHxH20m.y5123456789101213141516171819202122232425262728293031
12、32从以上例子可以看出描述符协议比较简单,一些常用的例子都被打包成了一些单独的函数调用。Properties,boundandunboundmethods,staticmethods和classmethods实现都基于描述符协议7 .Properties调用property()方法是创建一个数据描述符的简便方式。方法声明如下:property(fget=None,fset=None,fdel=None,doc=None)-propertyattribute使用方法如下:classC(object):11第5页共13页defgetx(self):returnself._xdefsetx(self
13、,value):self._x=valuedefdelx(self):delself._xx=property(getx,setx,delx,Tmthexproperty.)12345要想知道property()具体是怎样实现的,可以参考如下的Python代码classProperty(object):“EmulatePyProperty_Type()inObjects/descrobject.cdef_init_(self,fget=None,fset=None,fdel=None,doc=None):self.fget=fgetself.fset=fsetself.fdel=fdelsel
14、f._doc_=docdefget_(self,obj,objtype=None):ifobjisNone:returnselfifself.fgetisNone:raiseAttributeError,Hunreadableattributereturnself.fget(obj)def_set_(self,obj,value):ifself.fsetisNone:raiseAttributeError,ncantsetattribute1self.fset(obj,value)def_delete_(self,obj):ifself.fdelisNone:raiseAttributeError,cantdeleteattributeself.fdel(obj)12345678910111213141516171819202122232425property()方法可以做属性的安全校验或者代码的向前兼容。如下的Cell类,假如最开始时我能直接通过Cell(blO).value来操作value属性,如果后续我需要做一些限制并且不需要修改原来的代码逻辑,那我只要用