当前位置: 首页 > 后端技术 > Python

PythonDescriptor

时间:2023-03-25 22:49:05 Python

描述符的介绍从stackoverflow上关于描述符(descriptor)的问题开始。摄氏度类:def__get__(self,instance,owner):return5*(instance.fahrenheit-32)/9def__set__(self,instance,value):instance.fahrenheit=32+9*value/5classTemperature:celsius=Celsius()def__init__(self,initial_f):self.fahrenheit=initial_ft=Temperature(212)print(t.celsius)#Output100.0t.celsius=0print(t.fahrenheit)#Outputabove32.0代码实现温度在摄氏温度和华氏温度之间的自动转换。其中,Temperature类包含实例变量fahrenheit和类变量celsius,celsius用描述符Celsius表示。这段代码提出的三个问题:问题一:什么是描述符?问题2:__get__、__set__、__delete__三个方法的参数问题3:描述符有哪些应用场景?问题4:属性和描述符有什么区别?问题一:什么是描述符?描述符是一个类对象,它实现了__get__、__set__和__delete__的一个或多个方法。当一个类变量指向这样一个装饰器时,访问这个类变量会调用__get__方法,给这个类变量赋值会调用__set__方法。这个类变量称为描述符。描述符实际上是一种代理机制:当一个类变量被定义为一个描述符时,对这个类变量的操作将由这个描述符来表示。问题2:描述符类描述符的三个方法的参数:def__get__(self,instance,owner):print(instance)print(owner)return'desc'def__set__(self,instance,value):print(instance)print(value)def__delete__(self,instance):print(instance)classA:a=descriptor()delA().a#输出<__main__.Aobjectat0x7f3fc867cbe0>A().a#returndesc,output<__main__.Aobjectat0x7f3fc86741d0>,A.a#returndesc,outputNone,A().a=5#输出<__main__.Aobjectat0x7f3fc86744a8>,5A.a=5#直接修改A类的类变量,即a不再被descriptor描述符代理。从上面的输出结果可以看出,参数解释__get__(self,instance,owner)instance表示当前实例owner表示类本身,当使用类访问时,instance是None__set__(self,instance,value)instance表示类本身当前实例,值Rvalue,只有实例会调用__set____delete__(self,instance)instance表示当前实例三个方法的本质访问:instance.descriptor实际调用了descriptor.__get__(self,instance,owner)方法,以及需要返回一个Value赋值:instance.descriptor=value实际上调用了descriptor.__set__(self,instance,value)方法,返回值为None。删除:delinstance.descriptor实际上调用了descriptor.__delete__(self,obj_instance)方法,返回值为None问题3:descriptor有哪些应用场景?我们要创建一种新形式的实例属性,除了修改和访问之外,还有一些额外的功能,比如类型检查,数值验证等,都需要用到描述符《Python Cookbook》,即描述符主要是用于接管实例变量的操作。从functoolsimportpartialfromfunctoolsimportwrapsclassClassmethod():def__init__(self,fn):self.fn=fndef__get__(self,instance,owner):returnwraps(self.fn)(partial(self.fn,owner))将方法fn的第一个参数固定为实例的类。可参考python官方文档的另一种写法:descriptorclassClassMethod(object):def__init__(self,fn):self.fn=fndef__get__(self,instance,owner=None):ifownerisNone:owner=type(obj)defnewfunc(*args):returnself.f(owner,*args)returnnewfunc实现静态方法装饰器classStaticmethod:def__init__(self,fn):self.fn=fndef__get__(self,instance,cls):returnself.fn实现属性装饰器classProperty:def__init__(self,fget,fset=None,fdel=None,doc=''):self.fget=fgetself.fset=fsetself.fdel=fdelself.doc=docdef__get__(self,instance,owner):如果实例不是None:returnself.fget(instance)('无法设置')self.fset(instance,value)def__delete__(self,instance):如果不可调用(self.fdel):raiseAttributeError('cannotdelete')self.fdel(instance)defsetter(self,fset):self.fset=fsetreturnselfdefdeleter(self,fdel):self.fdel=fdel返回自用自定义属性描述华氏度和摄氏度类变量:classTemperature:def__init__(self,cTemp):self.cTemp=cTemp#有一个实例变量cTemp:celsiustemperaturedeffget(self):returnself.celsius*9/5+32deffset(self,value):self.celsius=(float(value)-32)*5/9deffdel(self):print('Farenheicannotdelete')farenheit=Property(fget,fset,fdel,doc='华氏温度')defcget(self):returnself.cTempdefcset(self,value):self.cTemp=float(value)defcdel(self):print('Celsiuscannotdelete')celsius=Property(cget,cset,cdel,doc='Celsiustemperature')useresult:t=Temperature(0)t.celsius#return0.0delt.celsius#outputCelsius不能deletet.celsius=5t.farenheit#return41.0t.farenheit=212t.celsius#return100.0delt.farenheit#OutputFarenhei不能删除使用装饰器装饰Temperature的farenheit和celsius两个属性:classTemperature:def__init__(self,cTemp):self.cTemp=cTemp@Property#celsius=Property(celsius)defcelsius(self):returnself.cTemp@celsius.setterdefcelsius(self,value):self.cTemp=value@celsius.deleterdefcelsius(self):打印('Celsius无法删除')@Property#farenheit=Property(farenheit)deffarenheit(self):returnself.celsius*9/5+32@farenheit.setterdeffarenheit(self,value):self.celsius=(float(value)-32)*5/9@farenheit.deleterdeffarenheit(self):print('Farenheitcannotdelete')使用结果与直接使用描述符描述类变量实现类变量的类型检查是一样的attribute首先实现一个类型检查DescriptorTypedclassTyped:def__init__(self,name,expected_type):#每个属性都有一个名字和对应的t输入self.name=nameself.expected_type=expected_typedef__get__(self,instance,cls):如果instance为None:returnselfreturninstance.__dict__[self.name]def__set__(self,instance,value):如果不是isinstance(value,self.expected_type):raiseTypeError('Attribute{}expected{}'.format(self.name,self.expected_type))instance.__dict__[self.name]=valuedef__delete__(self,instance):delinstance.__dict__[self.name]然后实际出现一个Person类,Person的属性name和age都由Typed来描述classPerson:name=Typed('name',str)age=Typed('age',int)def__init__(self,name:str,age:int):self.name=nameself.age=age类型检查过程:>>>Person.__dict__mappingproxy({'__dict__':,'__doc__':None,'__init__':,'__module__':'__main__','__weakref__':,'age':<__main__.Typedat0x7fe2f440bd68>,'name':<__main__.Typedat0x7fe2f440bc88>})>>>p=Person('suncle',18)>>>p.__dict__{'age':18,'名字':'孙叔叔'}>>>p=Person(18,'孙叔叔')--------------------------------------------------------------------------TypeErrorTraceback(最近一次通话last)()---->1p=Person(18,'suncle')在__init__(self,姓名,年龄)45def__init__(self,name:str,age:int):---->6self.name=name7self.age=agein__set__(self,instance,value)11def__set__(self,instance,value):12ifnotisinstance(value,self.expected_type):--->13raiseTypeError('Attribute{}expected{}'.format(self.name,self.expected_type))14instance.__dict__[self.name]=value15TypeError:Attributenameexpected但是,上述类型检查的方法存在一些问题。Person类可能有很多属性,所以每个属性都需要使用Typed描述符Describe一次我们可以写一个带参数的类装饰器来解决这个问题:deftypeassert(**kwargs):defwrap(cls):forname,expected_typeinkwargs.items():setattr(cls,name,Typed(name,expected_type))#经典写法returnclsreturnwrap然后使用typeassert类装饰器重新定义Person类:@typeassert(name=str,age=int)classPerson:def__init__(self,name,age):self.name=nameself.age=age可以看到typeassert类装饰器的参数是传递的属性名和类型的键值对在。如果我们想让typeassert类装饰器自动识别类的初始化参数类型并添加对应的类变量,可以使用inspect库和python类型注解来实现:importinspectdeftypeassert(cls):params=inspect.signature(cls).params.items()中名称参数的参数:如果param.annotation!=inspect._empty:setattr(cls,name,Typed(name,param.annotation))returncls@typeassertclassPerson:def__init__(self,name:str,age:int):#没有类型注解的参数将不会被管理self.name=nameself.age=age问题4:property和descriptor的区别我们可以利用Python内部的机制来获取和设置property值.一共有三种方法:Getters和Setters。我们可以使用方法来封装每个实例变量并获取和设置该实例变量的值。为了保证实例变量不被外部访问,可以将这些实例变量定义为private。因此,访问对象的属性需要显式函数:anObject.setPrice(someValue);对象.getValue()。财产。我们可以使用内置属性函数将getter、setter(和deleter)函数与属性名称绑定。因此,对属性的引用看起来和直接访问一样简单,但本质上是调用对象上的相应函数。例如,anObject.price=someValue;一个对象。值。描述符。我们可以将getter、setter(和deleter)函数绑定到一个类中。然后我们将该类的对象分配给属性名称。此时对每个属性的引用和直接访问一样,本质上是调用描述符对象对应的方法,例如anObject.price=someValue;一个对象。值。Getter和Setter的设计模式不够Pythonic。虽然它们在C++和JAVA中很常见,但是Python追求的是入门和直达。附1.data-descriptor和no-datadescriptor的中文翻译其实就是datadescriptor和non-datadescriptordata-descriptor:同时实现了__get__和__set__方法的descriptorno-datadescriptor:只实现了的区别__get__方法的描述符是无数据描述符的优先级低于instance.__dict__classInt:def__get__(self,instance,cls):return3classA:val=Int()def__init__(self):self.__dict__['val']=5A().val#return5datadescriptorhashigherprioritythaninstance.__dict__classInt:def__get__(self,instance,cls):return3def__set__(self,instance,value):passclassA:val=Int()def__init__(self):self.__dict__['val']=5A().val#return3附件二,描述符机制分析资料:官方文档-descriptorunderstanding-get-and-set-and-python-descriptorsanyisalin-Python-Descriptor精心整理comp的各个方向的视频课程和电子书uter,从入门、进阶、实用,按目录合理分类。你总能找到你需要的学习资料。你在等什么?立即关注并下载!!!念念不忘,必有回响,朋友们,请点个赞,万分感谢。我是职场亮哥,四年工作经验的YY高级软件工程师,拒绝当领导的斜杠程序员。听我说,我进步很大。如果有幸帮到你,请给我一个【点赞】,给我一个关注,如能评论鼓励,将不胜感激。职场凉阁文章列表:更多文章我的所有文章和回答均与版权保护平台合作,版权归职场凉阁所有。未经授权转载必究!