什么是描述符,描述符

2020-11-13 阅读 董元英整理

内容简介:特征描述符本文主要介绍Python中描述符的详细描述,它属于Python学习过程中类和对象的基本知识。您可以为您的朋友引用描述符,它是Python语言的一个深刻而重要的部格式描述符是什么...

这篇文章主要介绍了Python中的描述符详解,属于Python学习过程中类与对象的基本知识,需要的朋友可以参考下描述符(Descriptors)是Python语言中一个深奥但却重要的一部分。

它们广泛应用于Python语言的内核,熟练掌握描述符将会为Python程序员的工具箱添加一个额外的技巧。

为了给接下来对描述符的讨论做一些铺垫,我将描述一些程序员可能会在日常编程活动中遇到的场景,然后我将解释描述符是什么,以及它们如何为这些场景提供优雅的解决方案。

在这篇总结中,我会使用新样式类来指代Python版本。

1、假设一个程序中,我们需要对一个对象属性执行严格的类型检查。

然而,Python是一种动态语言,所以并不支持类型检查,但是这并不妨碍我们实现自己版本,且较为初级的类型检查。

对象属性类型检查的传统方法可能采用下面的方式:?123456789def__init__(self,name,age):ifisinstance(str,name):self.name=nameelse:raiseTypeError(Mustbeastring)ifisinstance(int,age):self.age=ageelse:raiseTypeError(Mustbeanint)上面是执行这种类型检查的一种方法,但是参数数量增加时它将变得比较繁琐。

另外,在赋值之前,我们可以创建一个在__init__中调用的type_check(type,val)函数,但是当我们想在其他地方设置属性值时,该如何简单地实现这种检查呢。

我想到的一个快速解决方案是Java中的getters和setters,但是这并不符合Python风格,并且比较麻烦。

2、假设在一个程序中,我们想创建一些在运行时立刻初始化然后变成只读的属性。

有人也能想到利用Python中的特殊方法来实现,但这种实现方法仍旧是笨拙和繁琐的。

3、最后,设想一个程序中,我们希望以某种方式自定义对象属性的访问。

例如需要记录这种属性的访问。

同样的,还是可以想到一个解决方法,即使这种解决方案可能比较笨重并且不可复用。

上述问题因都与属性引用相关而全部联系在了一起。

下面,我们将尝试自定义属性的访问方法。

Python描述符针对上面所列的问题,描述符提供了优雅、简洁、健壮和可重用的解决方案。

简而言之,一个描述符就是一个对象,该对象代表了一个属性的值。

这就意味着如果一个账户对象有一个属性name,那么描述符就是另一个能够用来代表属性name持有值的对象。

描述符协议中定义了__get__、__set__或__delete__这些特殊方法,描述符是实现其中一个或多个方法的对象。

这些方法中每一种方法的签名如下所示:?12345pythondescr.get(self,obj,type=None)-value。

descr.__set__(self,obj,value)--Nonedescr.__delete__(self,obj)--None实现__get__方法的对象是非数据描述符,意味着在初始化之后它们只能被读取。

而同时实现__get__和__set__的对象是数据描述符,意味着这种属性是可写的。

为了更好地理解描述符,我们给出针对上述问题基于描述符的解决方法。

使用Python描述符实现对象属性的类型检查将是一个非常简单的任务。

装饰器实现这种类型检查的代码如下所示:?1234567891011121314151617181920212223242526272829303132classTypedProperty(object):def__init__(self,name,type,default=None):self.name=_nameself.type=typeself.default=defaultifdefaultelsetype()def__get__(self,instance,cls):returngetattr(instance,self.name,self.default)def__set__(self,instance,value):ifnotisinstance(value,self.type):raiseTypeError(Mustbea%s%self.type)setattr(instance,self.name,value)def__delete__(self,instance):raiseAttributeError(Can'tdeleteattribute)classFoo(object):name=TypedProperty(name,str)num=TypedProperty(num,int,42)acct=Foo()acct.name=obiacct.num=1234printacct.num1234printacct.nameobi#tryingtoassignastringtonumberfailsacct.num='1234'TypeError:Mustbeatype'int'在这个例子中,我们实现了一个描述符TypedProperty,并且这个描述符类会对它所代表的类的任何属性执行类型检查。

注意到这一点很重要,即描述符只能在类级别进行合法定义,而不能在实例级别定义。

例如,在上面例子中的__init__方法里。

当访问类Foo实例的任何属性时,描述符会调用它的__get__方法。

需要注意的是,__get__方法的第一个参数是描述符代表的属性被引用的源对象。

当属性被分配时,描述符会调用它的__set__方法。

为了理解为什么可以使用描述符代表对象属性,我们需要理解Python中属性引用解析的执行方式。

对于对象来说,属性解析机制在object.__getattribute__()中。

该方法将b.x转换成type(b).__dict__['x'].__get__(b,type(b))。

然后,解析机制使用优先级链搜索属性,在优先级链中,类字典中发现的数据描述符的优先级高于实例变量,实例变量优先级高于非数据描述符,如果提供了getattr(),优先级链会为getattr()分配最低优先级。

对于一个给定的对象类,可以通过自定义__getattribute__方法来重写优先级链。

深刻理解优先级链之后,就很容易想出针对前面提出的第二个和第三个问题的优雅解决方案了。

那就是,利用描述符实现一个只读属性将变成实现数据描述符这个简单的情况了,即不带__set__方法的描述符。

尽管在本例中不重要,定义访问方式的问题只需要在__get__和__set__方法中增加所需的功能即可。

类属性每次我们想使用描述符的时候都不得不定义描述符类,这样看起来非常繁琐。

Python特性提供了一种简洁的方式用来向属性增加数据描述符。

一个属性签名如下所示:?1property(fget=None,fset=None,fdel=None,doc=None)-propertyattributefget、fset和fdel分别是类的getter、setter和deleter方法。

我们通过下面的一个示例来说明如何创建属性:?1234567891011121314classAccout(object):def__init__(self):self._acct_num=Nonedefget_acct_num(self):returnself._acct_numdefset_acct_num(self,value):self._acct_num=valuedefdel_acct_num(self):delself._acct_numacct_num=property(get_acct_num,set_acct_num,del_acct_num,Accountnumberproperty.)如果acct是Account的一个实例,acct.acct_num将会调用getter,acct.acct_num=value将调用setter,delacct_num.acct_num将调用deleter。

在Python中,属性对象和功能可以像描述符指南中说明的那样使用描述符协议来实现,如下所示:?123456789101112131415161718192021222324252627282930313233343536classProperty(object):EmulatePyProperty_Type()inObjects/descrobject.cdef__init__(self,fget=None,fset=None,fdel=None,doc=None):self.fget=fgetself.fset=fsetself.fdel=fdelifdocisNoneandfgetisnotNone:doc=fget.__doc__self.__doc__=docdef__get__(self,obj,objtype=None):ifobjisNone:returnselfifself.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__)Python也提供了@property装饰器,可以用它来创建只读属性。

一个属性对象拥有getter、setter和deleter装饰器方法,可以使用它们通过对应的被装饰函数的accessor函数创建属性的拷贝。

下面的例子最好地解释了这一点:?1234567891011121314151617classC(object):def__init__(self):self._x=None@property#thexproperty.thedecoratorcreatesaread-onlypropertydefx(self):returnself._x@x.setter#thexpropertysettermakesthepropertywriteabledefx(self,value):self._x=value@x.deleterdefx(self):delself._x如果我们想让属性只读,那么我们可以去掉setter方法。

在Python语言中,描述符有着广泛的应用。

Python函数、类方法、静态方法都是非数据描述符的例子。

针对列举的Python对象是如何使用描述符实现的问题,描述符指南给出了一个基本的描述。

作者给您推荐的内容
  1. 有时我们的手机会完全没有信号,出现仅限紧急呼叫的状况。那么该如何解除这种状况呢?下面小编给大家分享一下。01、首先我们打开手机进入设置界面,如下图所示,选择连接选项02、接下来进...

  2. 放置文字挂机游戏CSS不依赖于容器的底部来对齐文本布局参数,一个好的方法也更好即使用position属性来解决问题看下面的代码,利用位置的相对定位和绝对定位功能,很容易实现DI纯文...

  3. 小米手机的MIUI系统有着丰富的桌面小工具,但是很多第一次使用小米手机的朋友都不知道如何添加,其实只需要长按屏幕空白处就可以添加桌面小工具。接下来小编就告诉大家如何添加桌面小工具...

  4. 如何选择家用无线路由器...

  5. 化学腌割是什么意思实际上,中国移动是指中国移动通信。中国移动通信集团公司的英文名称是:中国移动通信集团公司提取每个英文单词的第一个字母,简称cmcc。将手机语言设置为英...

  6. 如何对word文档进行大纲级别设定?下面小编就给大家介绍其大纲级别的设定方法。01、首先打开或新建一个Word文档。02、紧接着点击视图。如图。03、然后再点击大纲视图。如下图。04、点击其左边...

  7. 让你们快速学会保存快手图片01、首先呢,打开手机桌面快手app呦(如图)02、选好你想保存的快手的照片那(如图)03、打开之后点击手机左上角分享(...

  8. 公文制作第一步:由于文档格式的特殊性,版面格式对纸张类型、页边距、文档网格、标题、文本类型和字体大小有明确的规定,因此具有一定的特殊性。1。用Word打开2004年度工word公文...

  9. 01、首先,我们打开手机后在手机上下载一个APP,名字叫做“葫芦侠”,下载好后打开葫芦侠。02、打开葫芦侠后,我们在搜索...

  10. 一些用户反映,他们的电脑会自动安装奇怪的杀毒软件,这非常烦人。我该怎么办电脑需win10什么杀毒软件好要下载杀毒软件吗?接下来,system city的小版本将教您如何通过修改注册表来...