描述符: descriptor
前情要点
调用实例对象属性时,obj.__getattribute__ 方法会首先拦截调用, __getattribute__ 会先从obj中找属性,然后到cls找属性,找不到会隐式调用__getattr__最终抛出异常(前提cls没有重写__getattr__方法)
一、描述符是什么
描述符本质就是一个新式类,是一个包含 “绑定行为” 的对象,对其属性的访问被描述器协议中定义的方法覆盖。在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
1 classFoo:2 #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
3 def __get__(self, instance, owner):4 print('__get__(),被执行了')5
6 def __set__(self, instance, value):7 print('__set__(),被执行了')8
9 def __delete__(self, instance):10 print('__delete__(),被执行了')
二、描述符的作用
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
classTest:
x=Foo()def __init__(self,x):
self.x=x
t= Test(2) #'__set__(),被执行了'
print(t.x) #'__get__(),被执行了' 'None'
三、描述符分为两种
1、数据描述符:至少实现了__delete__()或__set__()
2、非数据描述符:只有__get__()
#数据描述符
classFoo:def __set__(self, instance, value):print('set')def __get__(self, instance, owner):print('get')#非数据描述符
classFoo:def __get__(self, instance, owner):print('get')
四、注意事项:
* 描述符本身应该定义成新式类,被代理的类也应该是新式类
* 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
* 要严格遵循该优先级,优先级由高到底分别是
1 类属性(cls._attr)
2 数据描述符(cls.attr.__set__(),cls.attr.__get__())
3 实例属性(self.__dict__(attr))
4 非数据描述符(cls.attr.__get__())
5 找不到的属性触发__getattr__() ps:如果没有实现该方法则会抛出异常
五、描述符在实例和类中访问和赋值原理
1. 对于对象来说,描述器由cls.__getattribute__()调用,即obj.attr1 相当于 type(obj).__dict__["attr1"].__get__(obj, type(obj)),严格按照优先级链控制属性访问及赋值。
2. 如果cls.__getattribute__()没找到attr1,则会调用__getattr__()(如果实现了cls.__getattr__()则优先该方法,否则调用obj.__getattr__()抛出异常)
3. 类可以重写__getattribute__方法阻止描述器调用
def __getattribute__(self, key):"Emulate type_getattro() in Objects/typeobject.c"v= object.__getattribute__(self, key)if hasattr(v, '__get__'):return v.__get__(None, self)return v
4. 优先级链:例如访问obj.attr
1. 先查看obj的类是否有属性attr( type(obj).__dict__("attr") );如果有,看2;如果没有,看3
2. 判断attr是否为描述器;如果是,看4,如果不是,看3
3. 从obj.__dict__("attr")获取 如果有则返回该值,如果没有,看5
4. 如果attr是数据描述器且实现了__get__方法,返回该方法的返回值,否则看3
5. 如果attr实现了__get__方法,则返回该方法的返回值,否则看6
6. 触发__getattr__()( 如果实现了cls.__getattr__()则优先该方法,否则调用obj.__getattr__()抛出异常 )
六、描述符的应用
1. 现在有一个需求,定义一个用户信息,用户的名字为字符串类型,年龄为int类型,收入为float类型,可以用描述符来代理这些属性,
从而控制传入的数据类型。
#定义描述符
classDesc_type:def __get__(self, instance, owner):print('执行了__get__')print('instance是 %s'%instance) #instance表示的是被代理的类属性的类实例化出的对象,这里是p1
print('owner是 %s'%owner) #owner表示的是被代理的类,这里是People这个类
def __set__(self, instance, value):print('执行了__set__')print('instance是 %s'%instance) #instance表示的是被代理的类属性的类实例化出的对象,这里是p1
print('value是 %s'%value) #value表示的是被代理的类的属性的值,这里是'Menawey'
print('self=====%s'%self) #self表示的是描述符实例的对象Desc_type()--->name
def __delete__(self, instance):print('执行了__delete__')print('instance是 %s' %instance)#定义一个人的类(被代理的类)
classPeople:
name= Desc_type() #用描述符代理了name这个属性
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary
p1= People('Meanwey',24,11.1)print(p1.name) #会出发__get__
print(p1.__dict__) #{'age': 24, 'salary': 11.1}
发现被代理的name属性并没有被设置对应的值,所以__dict__中没有'name',那是因为实例化的时候执行了__init__,所以也执行了__set__,
但是在__set__中并没有真正的操作进行设置
2. 所以要想真正的对属性进行代理,对属性进行设置、获取和删除值,则需要通过操作底层__dict__字典,如下:
#定义描述符
classDesc_type:def __init__(self,key,value_type): #传入key用来操作底层属性字典,value_type用来表示期望的数据类型
self.key =key
self.value_type=value_typedef __get__(self, instance, owner):print('执行了__get__')return instance.__dict__[self.key] #return p2.name
def __set__(self, instance, value):print('执行了__set__',self)if not isinstance(value,str): #用来判断用户传入的是否符合要求
raise TypeError('%s 传入的不是 %s'%(self.key,self.value_type)) #抛出类型异常,提示用户程序终止
instance.__dict__[self.key] = value #符合要求,则设置属性对应的值
def __delete__(self, instance):print('执行了__delete__')
instance.__dict__.pop(self.key)#定义一个人的类(被代理的类)
classPeople:
name= Desc_type('name',str) #用描述符代理了name这个属性,相当于执行了Desc_type中的self.__set__
age = Desc_type('age',int)
salary= Desc_type('salary',float)def __init__(self, name, age, salary):
self.name=name
self.age=age
self.salary=salary
p2= People('Meanwey',24,11.1)#访问
print(p2.name)#赋值
p2.name = 'Jery'p2.age= 18.1 #没有传入整型的数据,则 ----TypeError: age 传入的不是
六、总结
* 类中的函数也是类的属性(可以将一个函数赋值给类的一个属性,和定义函数实现相同),Python中所有的函数都是一个非数据描述器,在访问属性(函数名)时__get__方法将其转换为方法并返回
* 静态方法可以由obj或cls调用,实际等效通过obj.__getattribute__(obj,"attr")或cls.__getattribute__(cls,"attr")
* 类方法在调用函数之前将类引用放在参数列表之前
* 描述符就是一个类(新式类)用来控制属性;
* 描述符分为数据描述和非数据描述符,区别在于前者有 __set__ 或 __delete__ 方法,后者没有;
* 描述符的使用要遵循优先级:类属性>数据描述符>实例属性>非数据描述符>找不到(__getattr__);
* 描述符方法中的self表示的是描述符实例化的对象,instance表示的是被描述(代理)的类实例化的对象,owner表示的是被描述(代理)的类,value表示的是设置到被描述(代理)属性的值。
* 函数也是描述器(只实现了__get__)
* 装饰器property也是描述器,实现 __get__,__set__,__delete__