目录
描述符
什么是描述符
描述符本质是一个类,在设计上至少实现了__set__() __get__() __delete__() 其中之一,这也被称为描述符协议。
__set__():为一个属性赋值时触发
__get__():获取一个属性时触发
__delete__():用del删除一个属性时触发
描述符的作用
把描述符定义成另一个类的属性,前提是不能定义到构造函数中,此时描述符就可以代理另一个类的属性。所以注意了,由描述符本身实例化的对象进行获取、赋值、删除操作并不会触发这三个方法。
描述符初探
#描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') #描述符Int class Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...') class People: name=Str() age=Int() def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age p1=People('alex',18) #输出结果 Str设置... Int设置... #描述符Str的使用 p1.name p1.name='egon' del p1.name #输出结果 Str调用 Str设置... Str删除.. #描述符Int的使用 p1.age p1.age=18 del p1.age #输出结果 Int调用 Int设置... Int删除... print(p1.__dict__) print(People.__dict__) # 输出结果可能让你费解 {} {'__module__': '__main__', 'name': <__main__.Str object at 0x00000033945DEA58>, 'age': <__main__.Int object at 0x00000033945DE2B0>, '__init__': <function People.__init__ at 0x0000003394753950>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None} #补充,看一下类型是否一致 print(type(p1) == People) print(type(p1).__dict__ == People.__dict__) True True
从初始化到一系列的调用、赋值、删除,我们都不难理解触发,但是在输出__dict__的时候,我们发现实例化的对象内容为空,类对象的内容输出就很明确的看到了其中的两个属性是类对象。所以我们可以自己理解为被代理的类自身只知道有什么属性,但是是什么样子的不清楚了,在代理那里保存着呢!
描述符也有自身要遵守的优先级问题:
-
类属性
-
数据描述符(至少实现了__get__()和__set__())
-
实例属性
-
非数据描述符(没有实现__set__())
-
找不到的属性触发__getattr__()
在此,不对优先级再做验证,出现问题是可以考虑是不是这方面原因。
描述符实战
实现用描述符实现类型控制
雏形
class Str: def __init__(self, name): self.name = name def __get__(self, instance, owner): print('get--->', instance, owner) return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->', instance, value) instance.__dict__[self.name] = value def __delete__(self, instance): print('delete--->', instance) instance.__dict__.pop(self.name) class People: name = Str('name') def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary p1 = People('egon', 18, 3231.3) # 调用 print(p1.__dict__) p1.name print(p1.name) # 赋值 print(p1.__dict__) p1.name = 'egonlin' print(p1.__dict__) # 删除 print(p1.__dict__) del p1.name print(p1.__dict__) set---> <__main__.People object at 0x000000D9C28CFC18> egon {'name': 'egon', 'age': 18, 'salary': 3231.3} get---> <__main__.People object at 0x000000D9C28CFC18> <class '__main__.People'> get---> <__main__.People object at 0x000000D9C28CFC18> <class '__main__.People'> egon {'name': 'egon', 'age': 18, 'salary': 3231.3} set---> <__main__.People object at 0x000000D9C28CFC18> egonlin {'name': 'egonlin', 'age': 18, 'salary': 3231.3} {'name': 'egonlin', 'age': 18, 'salary': 3231.3} delete---> <__main__.People object at 0x000000D9C28CFC18> {'age': 18, 'salary': 3231.3}
进阶1
如果我在刚才代码里用类名调用会报错,说instance是空,所以需要修改get方法:
def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name]
再次调用的时候就不会再报错了 输出:get---> None <class '__main__.People'>
进阶2 添加类型限制
class Str: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) if not isinstance(value,self.expected_type): #如果不是期望的类型,则抛出异常 raise TypeError('Expected %s' %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name) class People: name=Str('name',str) #新增类型限制str def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary p1=People(123,18,3333.3)#传入的name因不是字符串类型而抛出异常 set---> <__main__.People object at 0x00000053639A4438> 123 Traceback (most recent call last): File "D:/pycharm/model3/面向对象/描述符/磨刀霍霍.py", line 28, in <module> p1=People(123,18,3333.3)#传入的name因不是字符串类型而抛出异常 File "D:/pycharm/model3/面向对象/描述符/磨刀霍霍.py", line 24, in __init__ self.name=name File "D:/pycharm/model3/面向对象/描述符/磨刀霍霍.py", line 14, in __set__ raise TypeError('Expected %s' %str(self.expected_type)) TypeError: Expected <class 'str'>
逐一限制
class Typed: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) if not isinstance(value,self.expected_type): raise TypeError('Expected %s' %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name) class People: name=Typed('name',str) age=Typed('name',int) salary=Typed('name',float) def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary
但是如果属性很多,是不是这样子就有点麻烦
终极boss 类的装饰器
class Typed: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) if not isinstance(value,self.expected_type): raise TypeError('Expected %s' %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name) def typeassert(**kwargs): def decorate(cls): print('类的装饰器开始运行啦------>',kwargs) for name,expected_type in kwargs.items(): setattr(cls,name,Typed(name,expected_type)) return cls return decorate @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary print(People.__dict__) p1=People('egon',18,3333.3) 类的装饰器开始运行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>} {'__module__': '__main__', '__init__': <function People.__init__ at 0x000000BCA4A43950>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Typed object at 0x000000BCA48CEBA8>, 'age': <__main__.Typed object at 0x000000BCA4A3FC18>, 'salary': <__main__.Typed object at 0x000000BCA4A3FC88>} set---> <__main__.People object at 0x000000BCA4A3FCF8> egon set---> <__main__.People object at 0x000000BCA4A3FCF8> 18 set---> <__main__.People object at 0x000000BCA4A3FCF8> 3333.3