Python 的描述符的使用
2020-03-25 本文已影响0人
水之心
关于描述子的详细介绍见Python 之描述子。在此仅仅重申:如果一个对象定义了 __set__()
或者 __delete__()
,它将被视为一个数据描述子(data descriptor)。如果仅仅定义了 __get__()
该对象被称为非数据描述子(non-data descriptors)。
为什么要使用描述子呢?我们想象如下场景:使用 Python 模拟学生的成绩单,要求记录成绩、学号、姓名,并判断其是否通过考试。
您可能会这样写:
class ReportCard:
def __init__(self, name, student_id, score):
self.name = name
self.student_id = student_id
self.score = score
def check(self):
if self.score >= 60:
return '及格'
else:
return '失败'
这个实现看起来没有什么问题,但是如果录入分数的时候,录入了负值,如 -90
,上述的 check
将会作出一个错误的判断。为此,您会想到发起一个异常,即:
class ReportCard:
def __init__(self, name, student_id, score):
self.name = name
self.student_id = student_id
if score < 0:
raise ValueError("分数不能是负值!")
else:
self.score = score
def check(self):
if self.score >= 60:
return '及格'
else:
return '失败'
加入,成绩录入没有问题,而若重新修改成绩,此时也可能设定为负值,比如:
a = ReportCard('A', 11, 90)
a.score = -90
这样又该如何避免意外发生呢?其实,可以借助 property
修饰符:
class ReportCard:
def __init__(self, name, student_id, score):
self.name = name
self.student_id = student_id
if score < 0:
raise ValueError("分数不能是负值!")
else:
self._score = score
def check(self):
if self.score >= 60:
return '及格'
else:
return '失败'
@property
def score(self):
return self._score
@score.setter
def score(self, new_score):
if new_score < 0:
raise ValueError("分数不能是负值!")
else:
self._score = new_score
是不是很神奇?实际上,property 是基于 descriptor 而实现的。我们再考虑一个问题,录入成绩时,学号也可能录入为负值,:cry: 难道还要再次重复上述的工作?由此,可见 property 也存在局限性,且没有充分利用 Python 的“鸭子类型”的特性。既然,property 是基于 descriptor 而实现的,为什么不能直接从根源改变呢?
我们直接定义一个类,用于描述非负数:
class NonNegative:
def __init__(self):
self.dic = {}
def __get__(self, obj, objtype):
print('获得', obj)
return self.dic[obj]
def __set__(self, obj, value):
print('设定', obj, value)
if value < 0:
raise ValueError("不能是负值!")
self.dic[obj] = value
此时,定义成绩单将会十分简洁:
class ReportCard:
score = NonNegative()
std_id = NonNegative()
def __init__(self, name, student_id, score):
self.name = name
self.student_id = student_id
self.score = score
def check(self):
if self.score >= 60:
return '及格'
else:
return '失败'
![](https://img.haomeiwen.com/i1114626/a759f67591c855c3.png)