什么是描述符

描述符,是一个类,定义了另一个类的属性的访问方式,即一个类可以将属性管理委托给另一个类

特别注意:必须把描述符定义成这个类的类属性,不能为定义到构造函数中

描述符协议

描述符类基于三种特殊方法组成了描述符协议: set(self, instance, value):为一个属性赋值时触发(仅对实例起作用) 调用此方法以设置 instance 指定的所有者类的实例的属性为新值 value。

get(self, instance, owner=None):调用一个属性时触发(访问owner类或owner类实例对应属性) 调用此方法以获取所有者类的属性或该类的实例的属性(类属性访问/类实例属性访问) 可选的参数owner参数是所有者类, instance是用来访问属性的实例 owner:描述符对象所绑定的类 instance:假如用实例来访问描述符属性,该参数值为实例对象,如果通过类访问,该值为None

delete(self, instance):删除一个属性时触发 调用此方法以删除 instance 指定的所有者类的实例的属性

使用描述符

最常见的方式是把它的实例对象设置为其他类(常被称为owner类)的属性

实现语法

其中,类中实现了get()和set()的描述符称为数据描述符,只实现了get()的被称为非数据描述符

  1. class M:
  2. def __init__(self, x=1):
  3. self.x = x
  4. def __get__(self, instance, owner):
  5. print("调用__get__方法")
  6. return self.x
  7. def __set__(self, instance, value):
  8. print("调用__set__方法")
  9. self.x = value
  10. class AA:
  11. m = M(x=2) # 类属性m被M()代理
  12. if __name__ == '__main__':
  13. a = AA()
  14. print(a.m)
  15. a.m = 30
  16. print(a.m)
  17. -------------------------
  18. #调用__get__方法
  19. #2
  20. #调用__set__方法
  21. #调用__get__方法
  22. #30

由上例子可得,如果一个类中的某个属性有数据描述符,那么每次查找这个属性都会调用描述符的get()这个方法并返回其值,每次对这个属性进行赋值,都会调用描述符的set()方法

通过类、实例访问描述符

  1. class Descriptor(object):
  2. def __get__(self, instance, owner=None):
  3. print(f"Call __get__, instance: {instance}, owner: {owner}")
  4. # 当不是通过实例访问时,直接返回描述符对象
  5. if not instance:
  6. print(f"instance not exists, return {self}")
  7. return self
  8. return 'test descriptor'
  9. class Foo:
  10. bar = Descriptor()
  11. """通过类访问描述符"""
  12. # Call __get__, instance: None, owner: <class '__main__.Foo'>
  13. # instance not exists, return <__main__.Descriptor object at 0x7fa610adc5f8>
  14. Foo.bar
  15. """类实例访问描述符"""
  16. # Call __get__, instance: <__main__.Foo object at 0x7fa610adc630>, owner: <class '__main__.Foo'>
  17. Foo().bar

应用场景

实例1-1

  1. class Students:
  2. def __init__(self, name, math, chinese):
  3. self._chinese = chinese
  4. self._math = math
  5. self.name = name
  6. @property
  7. def math(self):
  8. return self._math
  9. @math.setter
  10. def math(self, value):
  11. if 0 <= value <= 100:
  12. self._math = value
  13. else:
  14. raise ValueError("valid error!")
  15. @property
  16. def chinese(self):
  17. return self._chinese
  18. @chinese.setter
  19. def chinese(self, value):
  20. if 0 <= value <= 100:
  21. self._chinese = value
  22. else:
  23. raise ValueError("valid error!")
  24. if __name__ == '__main__':
  25. std = Students("zaygee", '33', '67')
  26. print(std.name)
  27. print(std.math)
  28. ---------------------------------
  29. #zaygee
  30. #33
  31. #67

上面的例子类中两个类属性:math、chinese,都使用了property对属性的合法性进行了有效控制,就是代码太冗余了
当我们使用描述符对上面的例子进行重写

  1. class Score:
  2. def __init__(self, default=0):
  3. self._score = default
  4. def __set__(self, instance, value):
  5. if not isinstance(value, int):
  6. raise TypeError("sccore error!")
  7. if not 0 <= value <= 100:
  8. raise ValueError("valid error!")
  9. self._score = value
  10. def __get__(self, instance, owner):
  11. return self._score
  12. def __delete__(self):
  13. del self._score
  14. class Students:
  15. math = Score(0)
  16. chinese = Score(0)
  17. def __init__(self, name, math, chinese):
  18. self._chinese = chinese
  19. self._math = math
  20. self.name = name
  21. if __name__ == '__main__':
  22. std = Students("zaygee", '33', '67')
  23. print(std.name)
  24. print(std.math)
  25. print(std.chinese)
  26. ----------------------------------
  27. #zaygee
  28. #33
  29. #67

应用场景-解决所有实例共享描述符

以上例子中,因为math其实是类变量,所以创建多个实例访问math其实是相当于访问类变量,会造成创建多个实例共享了描述符的情况,可以修改将变量的访问作用于instance上,使得实例之间类变量访问的隔离性

  1. class Score:
  2. def __init__(self, subject):
  3. self.name = subject
  4. def __get__(self, instance, owner):
  5. print("Call __get__ func")
  6. # 变量的访问直接作用于instance上
  7. return instance.__dict__[self.name]
  8. def __set__(self, instance, value):
  9. if 0<= value <= 100:
  10. # 变量的设置也是一样作用于instance上
  11. instance.__dict__[self.name] = value
  12. else:
  13. raise ValueError

应用场景-一个类定义多个描述符覆盖问题

可通过使用 **set_name(self, owner, name)** 方法解决此问题 owner: 描述符对象当前绑定的类 name: 描述符所绑定的属性名称

  1. class Descriptor(object):
  2. def __init__(self, min_value: int = 0, max_value: int = 0):
  3. self.min_value = min_value
  4. self.max_value = max_value
  5. def __get__(self, instance, owner=None):
  6. print(f"Call __get__, instance: {instance}, owner: {owner}")
  7. # 当不是通过实例访问时,直接返回描述符对象
  8. if not instance:
  9. print(f"instance not exists, return {self}")
  10. return self
  11. return instance.__dict__.get('field')
  12. def __set__(self, instance, value):
  13. value = self._validate_value(value)
  14. instance.__dict__['field'] = value
  15. def _validate_value(self, value):
  16. try:
  17. value = int(value)
  18. except (KeyError, ValueError):
  19. raise ValueError('Value is not a valid integer!')
  20. if not self.min_value < value < self.max_value:
  21. raise ValueError("not self.min_value < value < self.max_value")
  22. return value
  23. class Foo:
  24. bar = Descriptor(min_value=0, max_value=3)
  25. bar2 = Descriptor(min_value=0, max_value=4)
  26. # Call __get__, instance: <__main__.Foo object at 0x7fa610adc630>, owner: <class '__main__.Foo'>
  27. r = Foo()
  28. r.bar = 2
  29. # Call __get__, instance: <__main__.Foo object at 0x7f1c8ce5c7f0>, owner: <class '__main__.Foo'>
  30. # 2
  31. print(r.bar)
  32. # Call __get__, instance: <__main__.Foo object at 0x7f1c8ce5c7f0>, owner: <class '__main__.Foo'>
  33. # 2
  34. print(r.bar2)
  35. """
  36. 上面Foo类的bar、bar2都使用了Descriptor描述符,但是两个字段出现了相互覆盖的情况,可通过__set_name__ 方法解决
  37. """
  38. class Descriptor(object):
  39. def __init__(self, min_value: int = 0, max_value: int = 0):
  40. self.min_value = min_value
  41. self.max_value = max_value
  42. def __set_name__(self, owner, name):
  43. # 将绑定属性名保存在描述符对象中
  44. # 对于bar = Descriptor(min_value=0, max_value=3),name就是 "bar"了
  45. print(f"Call __set_name__ fun ,owner: {owner}, name: {name}")
  46. self._name = name
  47. def __get__(self, instance, owner=None):
  48. print(f"Call __get__, instance: {instance}, owner: {owner}")
  49. # 当不是通过实例访问时,直接返回描述符对象
  50. if not instance:
  51. print(f"instance not exists, return {self}")
  52. return self
  53. return instance.__dict__.get(self._name)
  54. def __set__(self, instance, value):
  55. value = self._validate_value(value)
  56. instance.__dict__[self._name] = value
  57. def _validate_value(self, value):
  58. try:
  59. value = int(value)
  60. except (KeyError, ValueError):
  61. raise ValueError('Value is not a valid integer!')
  62. if not self.min_value < value < self.max_value:
  63. raise ValueError("not self.min_value < value < self.max_value")
  64. return value
  65. class Foo:
  66. bar = Descriptor(min_value=0, max_value=3)
  67. bar2 = Descriptor(min_value=0, max_value=4)
  68. # Call __set_name__ fun ,owner: <class '__main__.Foo'>, name: bar
  69. # Call __set_name__ fun ,owner: <class '__main__.Foo'>, name: bar2
  70. # Call __get__, instance: <__main__.Foo object at 0x7fa610adc630>, owner: <class '__main__.Foo'>
  71. r = Foo()
  72. r.bar = 2
  73. # Call __get__, instance: <__main__.Foo object at 0x7f1c8ce5c7f0>, owner: <class '__main__.Foo'>
  74. # 2
  75. print(r.bar)
  76. # Call __get__, instance: <__main__.Foo object at 0x7f1c8ce5c7f0>, owner: <class '__main__.Foo'>
  77. # None
  78. print(r.bar2)

实例1-2:以下类.属性调用方式,会报错:AttributeError: ‘NoneType’ object has no attribute ‘dict
原因:类去操作属性,会传None给instance
解决方法:if instance is None:return self

  1. class Str:
  2. def __init__(self, name):
  3. self.name = name
  4. def __get__(self, instance, owner):
  5. print("__get__-->", instance, owner)
  6. return instance.__dict__[self.name]
  7. def __set__(self, instance, value):
  8. print('__set__-->', instance, value)
  9. instance.__dict__[self.name] = value
  10. def __delete__(self, instance):
  11. print('__delete__-->', instance)
  12. instance.__dict__.pop(self.name)
  13. class People:
  14. name = Str('name')
  15. def __init__(self, name, age, salary):
  16. self.name = name
  17. self.age = age
  18. self.salary = salary
  19. print(People.name)
  20. -----------------
  21. # return instance.__dict__[self.name]
  22. #AttributeError: 'NoneType' object has no attribute '__dict__'
  23. #__get__--> None <class '__main__.People'>

描述符拓展

https://blog.csdn.net/qq_27825451/category_9282212.html

描述符是python中复杂属性访问的基础,它在内部被用于实现property、方法、类方法、静态方法和super类型

调用优先级:类属性>数据描述符>实例属性>非数据描述符>找不到的属性触发getattr()

数据描述符和非数据描述符的区别

两者最大的区别主要体现在 所绑定实例的属性存取优先级上 对于非数据描述符,可直接用 instance.attr = xxx,在实例级别重写描述符属性attr,让其读取逻辑不受get方法控制。 对于数据描述符,数据描述符所定义的属性存储逻辑比实例属性优先级高,无法直接重写修改