描述符使用
众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
描述符是可以实现大部分Python类特性中的底层魔法,包括@classmethod@staticmethod@property甚至是__slot__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件
1 描述符是什么:
描述符本质就是一个新式类,在这个新式类中,至少实现了以下__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
触发时机:在获取指定描述符操作的成员属性的值的时候触发 参数:1描述符对象本身,2描述符描述的属性所在的对象,描述符描述的对象的类 返回值:必须有,不然无法获取相应属性值 注意:仅在描述符中使用
__set__():为一个属性赋值时,触发
触发时机:在设置或者添加指定描述符操作的成员属性的时候触发 参数:1描述符对象本身,2描述符描述的属性所在的对象,3要设置的值 返回值:无 注意:仅在描述符中使用
__delete__():采用del删除属性时,触发
触发时机:在删除指定描述符操作的成员属性的时候触发 参数:1描述符对象本身,2描述符描述的属性所在的对象 返回值:无 注意:仅在描述符中使用。
2 描述符是干什么的:
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
3 描述符分两种
一 数据描述符:至少实现了__get__()和__set__()
1 class Foo:
2 def __set__(self, instance, value):
3 print('set')
4 def __get__(self, instance, owner):
5 print('get')
二 非数据描述符:没有实现__set__()
1 class Foo:
2 def __get__(self, instance, owner):
3 print('get')
小结:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类,代理类必须实现三个描述符当中其中一个方法。
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
案例:
class MSF(): #MSF自己定义的描述符
def __init__(self):
self.num = 20
#obj就是下面把这个被代理类实例化出的对象age
#cls就是自己MSF这个类。
def __get__(self, obj, cls):
return self.num
def __set__(self, obj, value):
if value > 100 or value < 1:
print("设置的值不在范围之内")
else:
self.num = value
def __delete__(self, obj):
if obj.allow_delete:
del self.num
else:
print('不允许删除')
class Student(): #代理的描述类就是一个新式类
age = MSF() #实例化对象,代理的类必须实例化上面被代理的类
allow_delete = True #根据情况设置是否允许删除
s = Student()
s.age = 90 #设置值的时候会触发__set__
print(s.age) #获取值的时候会触发__get__
del s.age #删除值的时候会触发__del__
print(s.age)
描述符优先级
class Foo:
def __get__(self, instance, owner):
print('===>get方法')
def __set__(self, instance, value):
print('===>set方法',instance,value)
instance.__dict__['x']=value #b1.__dict__
def __delete__(self, instance):
print('===>delete方法')
class Bar:
x=Foo() #在何地?
print(Bar.x) #类属性字典中找不到,触发描述符__get__方法,因为还没赋值,返回None
Bar.x=1 #设置类属性,类属性优先级 > 描述符优先级,不会触发描述符__set__方法
print(Bar.__dict__) #查看底层字典,键值对已写进去覆盖
print(Bar.x) #不会触发描述符__get__方法,因为优先找到了底层字典中的类属性值,类属性优先级 > 数据描述符,直接返回结果
b1=Bar()
b1.x #触发__get__,首先到实例化b1的字典中没有找到,再去类中的字典中找触发数据描述符,数据描述符优先级 > 实例属性
b1.x=1 #触发__set__,首先到实例化b1的字典中没有找到,再去类中的字典中设置触发数据描述符
del b1.x #触发__delete__,首先到实例化b1的字典中没有找到,再去类中的字典中删除触发数据描述符
Bar.x=111111111111 #设置类属性
b1.x #不会触发描述符__get__方法, 类属性优先级 > 实例属性
del Bar.x #删除类属性
b1.x #因为类属性被删除了,再找数据描述符字典里没找到,再找自己实例属性字典也没有,就报错
class Foo: #创建非数据描述符做对比
def __get__(self, instance, owner):
print('===>get方法')
class Bar:
x=Foo() #在何地?
def __getattr__(self, item):
print('----->')
b1=Bar()
b1.x=1 #键值对已添加到实例属性底层字典当中
print(b1.__dict__) #查看得到上面赋值的结果。如果以上加上__set__变成数据描述符方法,数据描述符优先级 > 非数据描述符,这里就没有值,变空字典了
b1.xxxxxxx #只有当调取一个没有的实例属性,就触发找不到时的方法__getattr__,找不到的属性__getattr__()优先级最低