第31条 用描述符改写需要复用的@property方法

上一讲 https://blog.csdn.net/minemine999/article/details/104499251讲到@property的用法,可以代替gettersetter方法。

Python内置的@property修饰器,有个明显的缺点,就是不便于复用。受它修饰的方法,无法为同一个类中的其他属性所复用,而且与之无关的类也无法复用这些方法。

例如,要编写一个类,验证学生的家庭作业成绩在0~100之间。

class Homwork(object):
    def __init__(self) -> None:
        self._grade = 0
    
    @property
    def grade(self):
        return self._grade
    
    @grade.setter
    def grade(self,value):
        if not (0<=value<=100):
            raise ValueError('Grade must be between 0 and 100')
        self._grade = value
if __name__ == "__main__":
    hw1 = Homwork()
    hw1.grade = 95

现在把这套验证逻辑放在考试成绩上面,而考试成绩是由多个科目所组成,那么每一个科目都要单独统计分数。如下所示:

class Exam(object):
    def __init__(self):
        self._written_grade = 0
        self._math_grade = 0
    
    @staticmethod
    def _check_grade(value):
        if not (0<=value<=100):
            raise ValueError('Grade must be between 0 and 100')
     
    @property
    def written_grade(self):
        return self._written_grade
    @written_grade.setter
    def written_grade(self,value):
        self._check_grade(value)
        self._written_grade = value
     
    @property
    def math_grade(self):
        return self._math_grade
    @math_grade.setter
    def math_grade(self,value):
        self._check_grade(value)
        self._math_grade = value

可以看出,该类每添加一个科目,那么就需要写一个@property来验证各科科目,非常繁琐。

Python描述符

Python描述符可以更好地实现上述功能。Python会对访问操作进行一定的转译,而这种转译方式是通过描述符协议来实现的。

任何实现了描述符协议的类都可以作为描述符类,其实例化对象为描述符。描述符协议为一组成员函数所定义,包括:
在这里插入图片描述
参数详解

  • __get__(self,instance,instance_type)
  • __set__(self,instance,value)
  • __delete__(self,instance)
class A(object):
    def __get__(self,instance,instance_type):
        print('A.get')
    def __set__(self,instance,value):
        print('A.set')

class B(object):
    x = A()

if __name__ == '__main__':
    t = B()
    t.x  ##调用__get__
    t.x = 100 ##调用__set__

可以看到,实例化类B后,调用对象t访问其属性x,会自动调用类A__get__方法,由输出信息可以看出:

  • self: 是A的实例对象,也是B的属性x
  • instance: 是B的实例对象,在上面的例子是实例化后的变量t
  • instance_type: 是B这个类,因为A是包含在它的内部,

所以B拥有所属权,其实A类就是一个描述符(描述符是一个类),因为类A定义了__get__, __set__方法 。

访问t.x的时候,为什么会直接去调用描述符的 __get__方法?
t为实例,访问t.x时,其转译方式如下:

  1. 先访问实例属性,如果实例没有该属性x,则转向访问同名的类属性
  2. 如果类属性x是一个描述符,则Python认为其对象遵循描述符协议。于是将 B.x转化为B.__dict__[‘x’].__get__(t, B)来访问 ;
  3. 最后,进入类A__get__方法,进行相应的操作。

描述符可以把同一套逻辑运用到类中的不同属性上面。

下面实现Grade描述符类的创建,但是此种方法实现考试成绩的验证逻辑是错误的。

class Grade(object):
    def __init__(self):
        self._value = 0
        
    def __get__(self,instance,instance_type):
        return self._value
        
    def __set__(self,instance,value):
        if not (0<=value<=100):
            raise ValueError('Grade must be between 0 and 100')
        self._value = value
        
class Exam(object):
    # Class attribute
    math_grade = Grade()
    written_grade = Grade()
    science_grade = Grade()
    
if __name__ == "__main__":
    first_exam = Exam()
    first_exam.written_grade = 98
    first_exam.science_grade = 100
    print(first_exam.written_grade)
    print(first_exam.science_grade)
    
    second_exam = Exam()
    second_exam.written_grade = 78
    print('second :',second_exam.written_grade)
    print('first:',first_exam.written_grade)
    

可以发现,当产生多个Exam实例时,second_exam的赋值会覆盖掉first_exam对象的值。这是由于,所有的Exam实例都是要共享同一份Grade实例

当程序在定义Exam类的时候,它会把Grade实例创建好,以后创建Exam实例时,就不再构建Grade了。

为了解决这个问题,可以把每个Exam实例所对应的值记录到Grade中,利用字典保存每个实例状态。

class Grade(object):
    def __init__(self):
        self._values = {}
    
    def __get__(self,instance,instance_type):
        if instance is None: return self
        return self._values.get(instance,0)
    
    def __set__(self,instance,value):
        if not 0<=value<=100:
            raise ValueError('Grade must be between 0 and 100')
        self._values[instance] = value

进一步,上述方法虽然能够实现多个实例的存储,但是考虑到Grade中的字典值,均保存实例对象的引用,造成实例引用计数无法清零,导致内存泄漏

解决这个问题的方法是使用Weakref模块中的WeakKey-Dictionary特殊字典。

import Weakref
class Grade(object):
    def __init__(self):
        self._values = WeakKeyDictionary()
    #...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值