Python描述符 (descriptor) 详解
本文内容纲要:
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__'of'Test'objects>,'__weakref__':<attribute'__weakref__'of'Test'objects>,'__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__'of'Test'objects>,'__weakref__':<attribute'__weakref__'of'Test'objects>,'__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__'of'Test'objects>,'__weakref__':<attribute'__weakref__'of'Test'objects>,'__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__'of'TestDesc'objects>,'__weakref__':<attribute'__weakref__'of'TestDesc'objects>,'__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