python学习交流

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 '失败'
使用案例
上一篇 下一篇

猜你喜欢

热点阅读