python 描述器_python 描述器

描述符: 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__

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值