1、什么是描述符?
python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有__get__(),__set__(),和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。
以上为官方定义,纯粹为了装逼使用,一般人看这些定义都有一种问候祖先的冲动!
没关系,看完本文,你就会理解什么叫描述符了!
2、讲解描述符前,先看一下属性:__dict__(每个对象均具备该属性)
作用:字典类型,存放本对象的属性,key(键)即为属性名,value(值)即为属性的值,形式为{attr_key:attr_value}
对象属性的访问顺序:
①.实例属性
②.类属性
③.父类属性
④.__getattr__()方法
以上顺序,切记切记!
1classTest(object): 2cls_val=1 3def__init__(self): 4self.ins_val=10 5 6 7>>>t=Test() 8>>>Test.__dict__ 9mappingproxy({__module__:__main__,cls_val:1,__init__:<functionTest.__init__at0x0000000002E35D08>,__dict__:<attribute__dict__ofTestobjects>,__weakref__:<attribute__weakref__ofTestobjects>,__doc__:None}) 10>>>t.__dict__ 11{ins_val:10} 12 13>>>type(x)==X 14True 15 16#更改实例t的属性cls_val,只是新增了该属性,并不影响类Test的属性cls_val 17>>>t.cls_val=20 18>>>t.__dict__ 19{ins_val:10,cls_val:20} 20>>>Test.__dict__ 21mappingproxy({__module__:__main__,cls_val:1,__init__:<functionTest.__init__at0x0000000002E35D08>,__dict__:<attribute__dict__ofTestobjects>,__weakref__:<attribute__weakref__ofTestobjects>,__doc__:None}) 22 23#更改了类Test的属性cls_val的值,由于事先增加了实例t的cls_val属性,因此不会改变实例的cls_val值(井水不犯河水) 24>>>Test.cls_val=30 25>>>t.__dict__ 26{ins_val:10,cls_val:20} 27>>>Test.__dict__ 28mappingproxy({__module__:__main__,cls_val:30,__init__:<functionTest.__init__at0x0000000002E35D08>,__dict__:<attribute__dict__ofTestobjects>,__weakref__:<attribute__weakref__ofTestobjects>,__doc__:None})从以上代码可以看出,实例t的属性并不包含cls_val,cls_val是属于类Test的。
3、魔法方法:__get__(),__set__(),__delete__()
方法的原型为:
①__get__(self,instance,owner)
②__set__(self,instance,value)
③__del__(self,instance)
那么以上的self,instanceowner到底指社么呢?莫急莫急,听我慢慢道来!
首先我们先看一段代码:
1#代码1 2 3classDesc(object): 4 5def__get__(self,instance,owner): 6print("__get__...") 7print("self:\t\t",self) 8print("instance:\t",instance) 9print("owner:\t",owner) 10print(=*40,"\n") 11 12def__set__(self,instance,value): 13print(__set__...) 14print("self:\t\t",self) 15print("instance:\t",instance) 16print("value:\t",value) 17print(=*40,"\n") 18 19 20classTestDesc(object): 21x=Desc() 22 23#以下为测试代码 24t=TestDesc() 25t.x 26 27#以下为输出信息: 28 29__get__... 30self:<__main__.Descobjectat0x0000000002B0B828> 31instance:<__main__.TestDescobjectat0x0000000002B0BA20> 32owner:<class__main__.TestDesc> 33========================================可以看到,实例化类TestDesc后,调用对象t访问其属性x,会自动调用类Desc的__get__方法,由输出信息可以看出:
①self:Desc的实例对象,其实就是TestDesc的属性x
②instance:TestDesc的实例对象,其实就是t
③owner:即谁拥有这些东西,当然是TestDesc这个类,它是最高统治者,其他的一些都是包含在它的内部或者由它生出来的
到此,我可以揭开小小的谜底了,其实,Desc类就是是一个描述符(描述符是一个类哦),为啥呢?因为类Desc定义了方法__get__,__set__.
所以,某个类,只要是内部定义了方法__get__,__set__,__delete__中的一个或多个,就可以称为描述符(^_^,简单吧)
说到这里,我们的任务还远远没有完成,还存在很多很多的疑点?
问题1.为什么访问t.x的时候,会直接去调用描述符的__get__()方法呢?
答:t为实例,访问t.x时,根据常规顺序,
首先:访问Owner的__getattribute__()方法(其实就是TestDesc.__getattribute__()),访问实例属性,发现没有,然后去访问父类TestDesc,找到了!
其次:判断属性x为一个描述符,此时,它就会做一些变动了,将TestDesc.x转化为TestDesc.__dict__[x].__get__(None,TestDesc)来访问
然后:进入类Desc的__get__()方法,进行相应的操作
问题2.从上面代码1我们看到了,描述符的对象x其实是类TestDesc的类属性,那么可不可以把它变成实例属性呢?
答:我说了你不算,你说了也不算,解释器说了算,看看解释器怎么说的。
1#代码2 2 3classDesc(object): 4def__init__(self,name): 5self.name=name 6 7def__get__(self,instance,owner): 8print("__get__...") 9print(name=,self.name) 10print(=*40,"\n") 11 12classTestDesc(object): 13x=Desc(x) 14def__init__(self): 15self.y=Desc(y) 16 17#以下为测试代码 18t=TestDesc() 19t.x 20t.y 21 22#以下为输出结果: 23__get__... 24name=x 25========================================咦,为啥没打印t.y的信息呢?
因为没有访问__get__()方法啊,哈哈,那么为啥没有访问__get__()方法呢?(问题真多)
因为调用t.y时刻,首先会去调用TestDesc(即Owner)的__getattribute__()方法,该方法将t.y转化为TestDesc.__dict__[y].__get__(t,TestDesc),但是呢,实际上TestDesc并没有y这个属性,y是属于实例对象的,所以,只能忽略了。
问题3.如果类属性的描述符对象和实例属性描述符的对象同名时,咋整?
答:还是让解释器来解释一下吧。
1#代码3 2 3classDesc(object): 4def__init__(self,name): 5self.name=name 6print("__init__():name=",self.name) 7 8def__get__(self,instance,owner): 9print("__get__()...") 10returnself.name 11 12def__set__(self,instance,value): 13self.value=value 14 15classTestDesc(object): 16_x=Desc(x) 17def__init__(self,x): 18self._x=x 19 20 21#以下为测试代码 22t=TestDesc(10) 23t._x 24 25#输入结果 26__init__():name=x 27__get__()...不对啊,按照惯例,t._x会去调用__getattribute__()方法,然后找到了实例t的_x属性就结束了,为啥还去调用了描述符的__get__()方法呢?
这就牵扯到了一个查找顺序问题:当Python解释器发现实例对象的字典中,有与描述符同名的属性时,描述符优先,会覆盖掉实例属性。
不信?来看一下字典:
1>>>t.__dict__ 2{} 3 4>>>TestDesc.__dict__ 5mappingproxy({__module__:__main__,_x:<__main__.Descobjectat0x0000000002B0BA20>,__init__:<functionTestDesc.__init__at0x0000000002BC59D8>,__dict__:<attribute__dict__ofTestDescobjects>,__weakref__:<attribute__weakref__ofTestDescobjects>,__doc__:None})怎么样,没骗你吧?我这人老好了,从来不骗人!
我们再将代码3改进一下,删除__set__()方法试试看会发生什么情况?
1#代码4 2 3classDesc(object): 4def__init__(self,name): 5self.name=name 6print("__init__():name=",self.name) 7 8def__get__(self,instance,owner): 9print("__get__()...") 10returnself.name 11 12classTestDesc(object): 13_x=Desc(x) 14def__init__(self,x): 15self._x=x 16 17 18#以下为测试代码 19t=TestDesc(10) 20t._x 21 22#以下为输出: 23__init__():name=x我屮艸芔茻,咋回事啊?怎么木有去调用__get__()方法?
其实,还是属性查找优先级惹的祸,只是定义一个__get__()方法,为非数据描述符,优先级低于实力属性的!!
问题4.什么是数据描述符,什么是非数据描述符?
答:一个类,如果只定义了__get__()方法,而没有定义__set__(),__delete__()方法,则认为是非数据描述符;反之,则成为数据描述符
问题5.天天提属性查询优先级,就不能总结一下吗?
答:好的好的,客官稍等!
①__getattribute__(),无条件调用
②数据描述符:由①触发调用(若人为的重载了该__getattribute__()方法,可能会调职无法调用描述符)
③实例对象的字典(若与描述符对象同名,会被覆盖哦)
④类的字典
⑤非数据描述符
⑥父类的字典
⑦__getattr__()方法
本文内容总结:
原文链接:https://www.cnblogs.com/Jimmy1988/p/6808237.html