为什么要使用描述符? 假设你正在 写一个成绩管理系统,没有太多编码经验的你,可能会这样写: class Student: def __init__(self, name, math, chinese, english): self.name = name self.math = math self.chinese = chinese self.english = english def __repr__(self): return "<Student: {}, math:{}, chinese: {}, english:{}>".format( self.name, self.math, self.chinese, self.english ) st1 = Student('小明', 88, 99, 35) st2 = Student('小红', 99, 78, 67) print(st1) print(st2)
输出结果:
<Student: 小明, math:88, chinese: 99, english:35>
<Student: 小红, math:99, chinese: 78, english:67>
这样看起来一切都很合理,但程序不像人那么智能,不会自动根据使用场景判断数据的合法性,如果老师输入成绩的时候,不小心将成绩录成了负数 或者超过成绩的最大值,程序是无法感知的。聪明的你马上想到了在代码中加入逻辑判断。
class Student: def __init__(self, name, math, chinese, english): self.name = name if 0 <= math <= 100: self.math = math else: raise ValueError("Valid value must be in [0, 100]") if 0 <= chinese <= 100: self.chinese = chinese else: raise ValueError("Valid value must be in [0, 100]") if 0 <= english <= 100: self.english = english else: raise ValueError("Valid value must be in [0, 100]") def __repr__(self): return "<Student: {}, math:{}, chinese: {}, english:{}>".format( self.name, self.math, self.chinese, self.english) st1 = Student('小明', 88, 99, -35) st2 = Student('小红', 99, 78, 67) print(st1) print(st2)
输出结果:
这下程序稍微有点人工智能,能够自己明辨是非了。但是在__init__ 里有太多的逻辑判断,很影响代码的可读性。巧的是你学习了 Property 特性,可以很好的应用在这里。于是你的代码修改如下:
class Student: def __init__(self, name, math, chinese, english): self.name = name self.math = math self.chinese = chinese self.english = english @property def math(self): return self._math @math.setter def math(self, value): if 0 <= value <= 100: self._math = value else: raise ValueError('Valid value must be in [0,100]') @property def chinese(self): return self._chinese @chinese.setter def chinese(self, value): if 0 <= value <= 100: self._chinese = value else: raise ValueError('Valid value must be in [0,100]') @property def english(self): return self._english @english.setter def english(self, value): if 0 <= value <= 100: self._english = value else: raise ValueError('Valid value must be in [0,100]') def __repr__(self): return "<Student:{},math:{},chinese:{},english:{}>". format(self.name, self.math, self.chinese, self.english)
代码的可读性瞬间提升了,功能上没啥问题,但是还是有点啰嗦,代码重复性太高,于是你开始研究更优雅的方式,接触到了描述符。什么是描述符? 一个实现了描述符协议的类就是一个描述符。
什么是描述符协议? 实现了__get__(),__set__(),__delete__()其中至少一个方法的类,就是一个描述符。
__get__():用于访问属性,他返回属性的值,若属性不存在,不合法等都可以抛出对应的异常
__set__():将在属性分配操作中调用。不会返回任何内容
__delete__():控制删除操作。不会返回任何内容。
对描述符有了大概了解之后,你开始重写上面方法。
from weakref import WeakKeyDictionary class Score(): """ score should in [0,100] WeakKeyDictionary:弱引用字典,能防止内存泄漏 """ def __init__(self): self.score = WeakKeyDictionary() # self.score = {} def __get__(self, instance, owner): # print(instance,'__get__') return self.score[instance] def __set__(self, instance, value): if not isinstance(value, int): raise TypeError('Score must be int') if 0 <= value <= 100: self.score[instance] = value else: raise ValueError("score not in [0,100]") class Student(): # 托管属性定义在类级别上 math = Score() chinese = Score() english = Score() def __init__(self, name, math, chinese, english): self.name = name self.math = math self.chinese = chinese self.english = english def __repr__(self): return "<Student:{},math:{},chinese:{},english:{}>". format(self.name, self.math, self.chinese, self.english) stu = Student("malong", 91, 77, 88) stu1 = Student("malong1", 57, 67, 78) print(stu) print(stu1) try: stu.english = -23 except ValueError : stu.english=99 print(stu)
对比一下结果,发现确实优雅。