Python高级用法之描述符

Python内置的@property修饰器有个明显的缺点,就是不便复用。受它修饰的方法,无法为同一个类中的其他属性复用。和属性相关的方法,只能在子类里面共享,而与之无关的其他类,也无法复用同一份代码。

例如:

class Homework(object):
    def __init__(self):
        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 ~ 100")
        else:
            self._grade = value

class Exam(object):
    """
    此处Exam不是Homework的子类,则无法使用Homework类中定义的grade方法。且Exam类中,每增加一个科目都需要添加
 property
    """
    def __init__(self):
        self._grade = 0

    @property
    def match_grade(self):
        return self._grade

    @match_grade.setter
    def match_grade(self, value):
        if not (0 <= value <= 100):
            raise ValueError("Grade must be between 0 ~ 100")
        else:
            self._grade = value

    @property
    def english_grade(self):
        return self._grade

    @english_grade.setter
    def english_grade(self, value):
        if not (0 <= value <= 100):
            raise ValueError("Grade must be between 0 ~ 100")
        else:
            self._grade = value

linlei_h = Homework()
linlei_h.grade = 80
print(linlei_h.grade)

linlei_e = Exam()
linlei_e.english_grade = 110

此种场景下,当我们需要复用属性方法时,就可以使用描述符。描述符类,即提供__get__和__set__方法的类。描述符能够把同一套逻辑运用在类中的不同属性上面。

1, 基本用法如下:

class Grade(object):
    def __init__(self):
        self._vaule = 0

    def __get__(self, instance, instance_type):
        return self._vaule

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError("Grade must be between 0 ~ 100")
        else:
            self._vaule = value

class Exam(object):
    # class attribute
    math_grade = Grade()
    english_grade = Grade()

exam = Exam()
exam.math_grade = 88
exam.english_grade = 99
print(exam.math_grade)
print(exam.english_grade)

但是注意,此时如果在多个Exam实例上分别操作属性,则会产生意想不到的错误结果。

class Grade(object):
    def __init__(self):
        self._vaule = 0

    def __get__(self, instance, instance_type):
        return self._vaule

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError("Grade must be between 0 ~ 100")
        else:
            self._vaule = value

class Exam(object):
    # class attribute
    math_grade = Grade()
    english_grade = Grade()

exam = Exam()
exam.math_grade = 88
exam.english_grade = 99

exam2 = Exam()
exam2.math_grade = 55
exam2.english_grade = 66

print(exam.math_grade)
print(exam.english_grade)
print(exam2.math_grade)
print(exam2.english_grade)

####输出结果为:
55   66   55   66

产生这个问题的原因是:对于match_grade这个类属性来说,所有的Exam实例都共享同一个Grade实例。而表示该属性的Grade实例,只会在程序的生命周期中构建一次。也就是说:当程序定义Exam类是,它会把Grade实例构建好,以后再创建Exam实例时,就不会再次构建Grade了。为了解决这个问题,我们需要把每个Exam的实例对应的值记录到Grade中。

class Grade(object):
    def __init__(self):
        self._vaule = {}

    def __get__(self, instance, instance_type):
        if instance is None:
            return self
        else:
            return self._vaule.get(instance, 0)

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError("Grade must be between 0 ~ 100")
        else:
            self._vaule[instance] = value

class Exam(object):
    # class attribute
    math_grade = Grade()
    english_grade = Grade()

exam = Exam()
exam.math_grade = 88
exam.english_grade = 99

exam2 = Exam()
exam2.math_grade = 55
exam2.english_grade = 66

print(exam.math_grade)
print(exam.english_grade)
print(exam2.math_grade)
print(exam2.english_grade)


###输出如下:
88
99
55
66

但是以上方法存在内存泄漏问题。在程序的生命周期中,传给__set__方法的每一个Exam实例,_value字典都会保存指向该实例的一份饮用。这会导致该实例的引用计数无法将为0,从而使垃圾收集器无法将其回收。

使用python自带的weakref模块中的WeakKeyDictionary特殊字典取代原来的普通字典可解决此类问题。WeakKeyDictionary的作用:当程序生命周期中,没有指向Exam实例的强引用时,系统会自动将实例从字典的键中移除。

weakref的用法参考:http://ju.outofmemory.cn/entry/367661

只需要改进Grad类即可解决内存泄漏问题:


import weakref
class Grade(object):
    def __init__(self):
        ##弱引用的一个主要用途就是来实现缓存或者大对象的映射。就是当其他地方没有对这些大文件的引用的时候,这个对象会被销毁。
        self._vaule = weakref.WeakKeyDictionary()
       
    def __get__(self, instance, instance_type):
        if instance is None:
            return self
        else:
            return self._vaule.get(instance, 0)

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError("Grade must be between 0 ~ 100")
        else:
            self._vaule[instance] = value

class Exam(object):
    # class attribute
    math_grade = Grade()
    english_grade = Grade()

exam = Exam()
exam.math_grade = 88
exam.english_grade = 99

exam2 = Exam()
exam2.math_grade = 55
exam2.english_grade = 66

print(exam.math_grade)
print(exam.english_grade)
print(exam2.math_grade)
print(exam2.english_grade)

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值