python @property,description,修饰符用法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/peiwang245/article/details/78360849

        Python中的@property与Descriptor的方法可以为属性添加约束或进行参数检查, 方便简洁, 而网络上的介绍相对较为杂乱, 本文对它们做一个清晰的梳理.


一. 典型例子:

        我们通常使用类定义一些实体, 比如学生成绩(0~100分):

[python] view plain copy
print ?
  1. class student(object):  
  2.     def __init__(self, score):  
  3.         self.score = score  
class student(object):
    def __init__(self, score):
        self.score = score
        有了类定义, 老师可以方便的录入每位学生成绩, 但有一个很大的缺陷: 程序允许录入负数, 或超过100的分数. 为了解决这个问题, 有一个简单的解决方案:

[python] view plain copy
print ?
  1. class student(object):  
  2.   
  3.     def __init__(self, score):  
  4.         if not isinstance(score, int):  
  5.             raise ValueError(‘score must be an integer’)  
  6.         if score < 0 or score > 100:  
  7.             raise ValueError(‘score must between 0~100’)  
  8.         self.score = score  
class student(object):

    def __init__(self, score):
        if not isinstance(score, int):
            raise ValueError('score must be an integer')
        if score < 0 or score > 100:
            raise ValueError('score must between 0~100')
        self.score = score
        这样一来, 如果成绩录入为非整数或者不在0~100分之间的成绩时, 程序提示ValueError. 可是如果使用者要进行成绩修改, 函数__init__中的限制将不起任何作用, 成绩还是可能被修改为不正确的值.

[python] view plain copy
print ?
  1. Jack = Student()  
  2. Jack.score = 9999  
Jack = Student()
Jack.score = 9999

        这类问题称为参数检查问题, 解决这类问题, 有几种方法, 本文将由浅入深一一介绍.


二. 私有属性+类方法

        程序可以通过属性访问限制与类方法相结合的方式解决问题:

[python] view plain copy
print ?
  1. class student(object):  
  2.   
  3.     def get_score(self):  
  4.         return self.__score  
  5.   
  6.     def set_score(self, score):  
  7.         if not isinstance(score, int):  
  8.             raise ValueError(‘score must be an integer’)  
  9.         if score < 0 or score > 100:  
  10.             raise ValueError(‘score must between 0~100’)  
  11.         self.__score = score  
class student(object):

    def get_score(self):
        return self.__score

    def set_score(self, score):
        if not isinstance(score, int):
            raise ValueError('score must be an integer')
        if score < 0 or score > 100:
            raise ValueError('score must between 0~100')
        self.__score = score

        实例变量名如果以__开头,就变成了私有变量, 外部不能直接访问(系统讲变量名进行了修改变为了_student__score), 只能通过类方法的方式访问或修改属性. 如此无论是初始化还是成绩修改都必须经过参数检查.


三. @property

        通过以上方案虽然解决了问题, 但调用类方法丢失了操作的便捷性, 并且如果有其他属性也要做检查时, 要重新定义这些方法, 代码将臃肿不堪. Python提供了一种装饰器的方法, 可以对类动态的添加功能. @property便可以把函数调用伪装成属性的访问, 这样既进行了参数检查, 又可以方便的调用或修改属性值.

[python] view plain copy
print ?
  1. class student(object):  
  2.  
  3.     @property  
  4.     def score(self):  
  5.         return self._score  
  6.  
  7.     @score.setter  
  8.     def score(self, score):  
  9.         if not isinstance(score, int):  
  10.             raise ValueError(‘score must be an integer’)  
  11.         if score < 0 or score > 100:  
  12.             raise ValueError(‘score must between 0~100’)  
  13.         self._score = score  
  14.   
  15. jack = student()  
  16. jack.score = 99  
  17. print jack.score  
class student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, score):
        if not isinstance(score, int):
            raise ValueError('score must be an integer')
        if score < 0 or score > 100:
            raise ValueError('score must between 0~100')
        self._score = score

jack = student()
jack.score = 99
print jack.score


四. Descriptor

        @property的方法虽然简单有效, 但它们不能被重复使用, 如果除了score需要进行参数检查, 学生年龄也许要检查, 则需要定义多个@property的方法, 代码重用效率较低. 类内代码无法做到简介漂亮. descriptor是property的升级版, 允许为重复的参数检查编写单独的类来处理. descriptor的工作流程如下:

[python] view plain copy
print ?
  1. class ParameterCheck(object):  
  2.   
  3.     def __init__(self, score):  
  4.         self.default = score  
  5.   
  6.     def __get__(self, instance, owner):  
  7.         return self.default  
  8.   
  9.     def __set__(self, instance, value):  
  10.         if not isinstance(value, int):  
  11.             raise ValueError(‘value must be an integer’)  
  12.         if value < 0 or value > 100:  
  13.             raise ValueError(‘value must between 0~100’)  
  14.         self.default = value  
  15.   
  16. class student(object):  
  17.     score = ParameterCheck(0)  
  18.     age = ParameterCheck(0)  
  19.   
  20. Jack = student()  
  21. Jack.score = 80  
  22. Jack.age = 20  
  23. print Jack.score  
  24. print Jack.age  
class ParameterCheck(object):

    def __init__(self, score):
        self.default = score

    def __get__(self, instance, owner):
        return self.default

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        self.default = value

class student(object):
    score = ParameterCheck(0)
    age = ParameterCheck(0)

Jack = student()
Jack.score = 80
Jack.age = 20
print Jack.score
print Jack.age
        ParameterCheck就是上文所谓的descripor, 其中__get__/__set__方法是描述符方法, 当解释器遇到print Jack.score时, 它就会自动把score当做一个带有__get__方法的描述符, 调用student.score.__get__方法并返回default值, 而赋值时, __set__方法的调用过程类似, 另外还存在__det__方法, 调用del Jack.age时, 自动删除属性.


五. Descripor中的代码坑

        由于描述符是以单独类的形式出现的, 所以可以有效的解决了代码@property臃肿的问题. 但不要认为descriptor如此简单, 因为Python语言本身设计的结构, 这里有几个代码坑需要注意:


1. 描述符必须在class level的位置

[python] view plain copy
print ?
  1. class ParameterCheck(object):  
  2.   
  3.     def __init__(self, score):  
  4.         self.default = score  
  5.   
  6.     def __get__(self, instance, owner):  
  7.         return self.default  
  8.   
  9.     def __set__(self, instance, value):  
  10.         if not isinstance(value, int):  
  11.             raise ValueError(‘value must be an integer’)  
  12.         if value < 0 or value > 100:  
  13.             raise ValueError(‘value must between 0~100’)  
  14.         self.default = value  
  15.   
  16.   
  17. class student(object):  
  18.   
  19.     def __init__(self):  
  20.         self.score = ParameterCheck(0)  
  21.   
  22. Jack = student()  
  23. Jack.score = 800  
  24. print “Jack.score = %s” % Jack.score  
class ParameterCheck(object):

    def __init__(self, score):
        self.default = score

    def __get__(self, instance, owner):
        return self.default

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        self.default = value


class student(object):

    def __init__(self):
        self.score = ParameterCheck(0)

Jack = student()
Jack.score = 800
print "Jack.score = %s" % Jack.score

        以上代码片将描述符放到__init__的函数中, 由打印结果看出, 程序并没有进行描述符中的参数检查. 所以描述符必须放到类的层次上才能正常工作, 不然python无法自动调用描述符中的方法.


2. 确保实例的数据只属于实例本身

        其实, Descriptor那一节为了简单起见, 描述符的内容写的过于随便, 当然在那一节中程序并没有问题, 但如果实例化两个学生的成绩 

[python] view plain copy
print ?
  1. Jack = student()  
  2. Tom = student()  
  3. Jack.score = 88  
  4. print “’”“’” * 10, “result”, ”’” * 10  
  5. print “Jack.score =”, Jack.score  
  6. Tom.score = 59  
  7. print “Jack.score =”, Jack.score  
  8.   
  9. ””””” result ”’”””‘  
  10. Jack.score = 88  
  11. Jack.score = 59  
Jack = student()
Tom = student()
Jack.score = 88
print "'" * 10, "result", "'" * 10
print "Jack.score =", Jack.score
Tom.score = 59
print "Jack.score =", Jack.score

'''''''''' result ''''''''''
Jack.score = 88
Jack.score = 59
        从运行结果可以看出, 修改实例Tom的分数会造成Jack分数的变动, 这也是Python设计让人觉得不爽的一处, 解决方法之一是使用一个字典单独保存专属于实例的数据:

[python] view plain copy
print ?
  1. from weakref import WeakKeyDictionary  
  2.   
  3. class ParameterCheckDict(object):  
  4.   
  5.     def __init__(self, score):  
  6.         self.default = score  
  7.         self.InstanceDict = WeakKeyDictionary()  
  8.   
  9.     def __get__(self, instance, owner):  
  10.         return self.InstanceDict.get(instance, self.default)  
  11.   
  12.     def __set__(self, instance, value):  
  13.         if not isinstance(value, int):  
  14.             raise ValueError(‘value must be an integer’)  
  15.         if value < 0 or value > 100:  
  16.             raise ValueError(‘value must between 0~100’)  
  17.         self.InstanceDict[instance] = value  
  18.         # print self.InstanceDict.items()  
  19.   
  20.   
  21. class student(object):  
  22.     score = ParameterCheckDict(0)  
  23.   
  24. Jack = student()  
  25. Tom = student()  
  26. Jack.score = 88  
  27. print “’”“’” * 10, “result”, ”’” * 10  
  28. print “Jack.score =”, Jack.score  
  29. Tom.score = 59  
  30. print “Jack.score =”, Jack.score  
  31.   
  32. ””””” result ”’”””‘  
  33. Jack.score = 88  
  34. Jack.score = 88  
from weakref import WeakKeyDictionary

class ParameterCheckDict(object):

    def __init__(self, score):
        self.default = score
        self.InstanceDict = WeakKeyDictionary()

    def __get__(self, instance, owner):
        return self.InstanceDict.get(instance, self.default)

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        self.InstanceDict[instance] = value
        # print self.InstanceDict.items()


class student(object):
    score = ParameterCheckDict(0)

Jack = student()
Tom = student()
Jack.score = 88
print "'" * 10, "result", "'" * 10
print "Jack.score =", Jack.score
Tom.score = 59
print "Jack.score =", Jack.score

'''''''''' result ''''''''''
Jack.score = 88
Jack.score = 88
        PS: 使用WeakKeyDictionary的目的是为了防止内存泄露.


3. 描述符的所有者为unhashable对象

        由于字典自身的特性, 以下代码段将报错:

[python] view plain copy
print ?
  1. class student(list):  
  2.     score = ParameterCheckDict(0)  
  3.   
  4. Jack = student()  
  5. print “’”“’” * 10, “result”, ”’” * 10  
  6. print Jack.score  
  7.   
  8. ””””” result ”’”””‘  
  9. TypeError: unhashable type: ’student’  
class student(list):
    score = ParameterCheckDict(0)

Jack = student()
print "'" * 10, "result", "'" * 10
print Jack.score

'''''''''' result ''''''''''
TypeError: unhashable type: 'student'
        这是因为student是list的子类, Jack实例是不可哈希的, 因此不可作为Jack.score.InstanceDict的key. 解决这个问题最好的方法是给描述符加标签:

[python] view plain copy
print ?
  1. class ParameterCheckLabel(object):  
  2.   
  3.     def __init__(self, label):  
  4.         self.label = label  
  5.   
  6.     def __get__(self, instance, owner):  
  7.         return instance.__dict__.get(self.label)  
  8.   
  9.     def __set__(self, instance, value):  
  10.         if not isinstance(value, int):  
  11.             raise ValueError(‘value must be an integer’)  
  12.         if value < 0 or value > 100:  
  13.             raise ValueError(‘value must between 0~100’)  
  14.         instance.__dict__[self.label] = value  
  15.         # print self.InstanceDict.items()  
  16.   
  17.   
  18. class student(list):  
  19.     score = ParameterCheckLabel(’score’)  
  20.   
  21. Jack = student()  
  22. Tom = student()  
  23. Jack.score = 30  
  24. print “’”“’” * 10, “result”, ”’” * 10  
  25. Tom.score = 40  
  26. print Jack.score  
class ParameterCheckLabel(object):

    def __init__(self, label):
        self.label = label

    def __get__(self, instance, owner):
        return instance.__dict__.get(self.label)

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        instance.__dict__[self.label] = value
        # print self.InstanceDict.items()


class student(list):
    score = ParameterCheckLabel('score')

Jack = student()
Tom = student()
Jack.score = 30
print "'" * 10, "result", "'" * 10
Tom.score = 40
print Jack.score
        以上代码片确实解决了字典存储的问题, 同样是字典存储, 之前的字典存储的Key是instance(例如Jack), 这里的字典存储的Key是属性值(例如score), 请求Jack.score时, 系统自动调用Jack.__dict__[‘score’], 但这段代码是特别脆弱的:

[python] view plain copy
print ?
  1. class student(list):  
  2.     score = ParameterCheckLabel(”value”)  
  3.   
  4. Jack = student()  
  5. Jack.score = 30  
  6. print “’”“’” * 10, “result”, ”’” * 10  
  7. Jack.value = 40  
  8. print Jack.score  
  9.   
  10. ””””” result ”’”””‘  
  11. 40  
class student(list):
    score = ParameterCheckLabel("value")

Jack = student()
Jack.score = 30
print "'" * 10, "result", "'" * 10
Jack.value = 40
print Jack.score

'''''''''' result ''''''''''
40

        虽然如此, 当遇到不可哈希的对象时, 这种方法还是很普遍. 由于此类问题可能造成不必要的麻烦, 可以采用元类的方法将属性的标签自动定义为属性变量:

[python] view plain copy
print ?
  1. class ParameterCheckLabel(object):  
  2.   
  3.     def __init__(self):  
  4.         self.label = None  
  5.   
  6.     def __get__(self, instance, owner):  
  7.         return instance.__dict__.get(self.label, None)  
  8.   
  9.     def __set__(self, instance, value):  
  10.         if not isinstance(value, int):  
  11.             raise ValueError(‘value must be an integer’)  
  12.         if value < 0 or value > 100:  
  13.             raise ValueError(‘value must between 0~100’)  
  14.         instance.__dict__[self.label] = value  
  15.   
  16.   
  17. class DescriptorOwner(type):  
  18.   
  19.     def __new__(cls, name, bases, attrs):  
  20.         for n, v in attrs.items():  
  21.             if isinstance(v, ParameterCheckLabel):  
  22.                 v.label = n  
  23.         return super(DescriptorOwner, cls).__new__(cls, name, bases, attrs)  
  24.   
  25.   
  26. class student(list):  
  27.     __metaclass__ = DescriptorOwner  
  28.     score = ParameterCheckLabel()  
  29.   
  30. Jack = student()  
  31. Jack.score = 30  
  32. print “’”“’” * 10, “result”, ”’” * 10  
  33. print Jack.score  
  34.   
  35. ””””” result ”’”””‘  
  36. 30  
class ParameterCheckLabel(object):

    def __init__(self):
        self.label = None

    def __get__(self, instance, owner):
        return instance.__dict__.get(self.label, None)

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        instance.__dict__[self.label] = value


class DescriptorOwner(type):

    def __new__(cls, name, bases, attrs):
        for n, v in attrs.items():
            if isinstance(v, ParameterCheckLabel):
                v.label = n
        return super(DescriptorOwner, cls).__new__(cls, name, bases, attrs)


class student(list):
    __metaclass__ = DescriptorOwner
    score = ParameterCheckLabel()

Jack = student()
Jack.score = 30
print "'" * 10, "result", "'" * 10
print Jack.score

'''''''''' result ''''''''''
30
        但这样大大增加了代码的复杂度, 要不要采用元类的方法视情况而定.


六. 属性作为字典Key__求批评指正

        对于unhashable的对象, 既然不能作为字典的key值, 那么我们换一种思路, 把实例的属性对象直接作为字典的key值, 这样不论对象是不是可哈希的, 都可以用描述符了, 也不存在上文所提到的为属性打标签的问题. 代码如下:

[python] view plain copy
print ?
  1. class ParameterCheckLabel(object):  
  2.   
  3.     def __get__(self, instance, owner):  
  4.         return instance.__dict__.get(self)  
  5.   
  6.     def __set__(self, instance, value):  
  7.         if not isinstance(value, int):  
  8.             raise ValueError(‘value must be an integer’)  
  9.         if value < 0 or value > 100:  
  10.             raise ValueError(‘value must between 0~100’)  
  11.         instance.__dict__[self] = value  
  12.   
  13.   
  14. class student(list):  
  15.     score = ParameterCheckLabel()  
  16.     age = ParameterCheckLabel()  
  17.   
  18. Jack = student()  
  19. Jack.score = 30  
  20. print “’”“’” * 10, “result”, ”’” * 10  
  21. Jack.age = 90  
  22. print “Jack.age =”, Jack.age  
  23. print “Jack.score =”, Jack.score  
  24.   
  25. ””””” result ”’”””‘  
  26. Jack.age = 90  
  27. Jack.score = 30  
class ParameterCheckLabel(object):

    def __get__(self, instance, owner):
        return instance.__dict__.get(self)

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        instance.__dict__[self] = value


class student(list):
    score = ParameterCheckLabel()
    age = ParameterCheckLabel()

Jack = student()
Jack.score = 30
print "'" * 10, "result", "'" * 10
Jack.age = 90
print "Jack.age =", Jack.age
print "Jack.score =", Jack.score

'''''''''' result ''''''''''
Jack.age = 90
Jack.score = 30

        Python中的@property与Descriptor的方法可以为属性添加约束或进行参数检查, 方便简洁, 而网络上的介绍相对较为杂乱, 本文对它们做一个清晰的梳理.


一. 典型例子:

        我们通常使用类定义一些实体, 比如学生成绩(0~100分):

[python] view plain copy
print ?
  1. class student(object):  
  2.     def __init__(self, score):  
  3.         self.score = score  
class student(object):
    def __init__(self, score):
        self.score = score
        有了类定义, 老师可以方便的录入每位学生成绩, 但有一个很大的缺陷: 程序允许录入负数, 或超过100的分数. 为了解决这个问题, 有一个简单的解决方案:

[python] view plain copy
print ?
  1. class student(object):  
  2.   
  3.     def __init__(self, score):  
  4.         if not isinstance(score, int):  
  5.             raise ValueError(‘score must be an integer’)  
  6.         if score < 0 or score > 100:  
  7.             raise ValueError(‘score must between 0~100’)  
  8.         self.score = score  
class student(object):

    def __init__(self, score):
        if not isinstance(score, int):
            raise ValueError('score must be an integer')
        if score < 0 or score > 100:
            raise ValueError('score must between 0~100')
        self.score = score
        这样一来, 如果成绩录入为非整数或者不在0~100分之间的成绩时, 程序提示ValueError. 可是如果使用者要进行成绩修改, 函数__init__中的限制将不起任何作用, 成绩还是可能被修改为不正确的值.

[python] view plain copy
print ?
  1. Jack = Student()  
  2. Jack.score = 9999  
Jack = Student()
Jack.score = 9999

        这类问题称为参数检查问题, 解决这类问题, 有几种方法, 本文将由浅入深一一介绍.


二. 私有属性+类方法

        程序可以通过属性访问限制与类方法相结合的方式解决问题:

[python] view plain copy
print ?
  1. class student(object):  
  2.   
  3.     def get_score(self):  
  4.         return self.__score  
  5.   
  6.     def set_score(self, score):  
  7.         if not isinstance(score, int):  
  8.             raise ValueError(‘score must be an integer’)  
  9.         if score < 0 or score > 100:  
  10.             raise ValueError(‘score must between 0~100’)  
  11.         self.__score = score  
class student(object):

    def get_score(self):
        return self.__score

    def set_score(self, score):
        if not isinstance(score, int):
            raise ValueError('score must be an integer')
        if score < 0 or score > 100:
            raise ValueError('score must between 0~100')
        self.__score = score

        实例变量名如果以__开头,就变成了私有变量, 外部不能直接访问(系统讲变量名进行了修改变为了_student__score), 只能通过类方法的方式访问或修改属性. 如此无论是初始化还是成绩修改都必须经过参数检查.


三. @property

        通过以上方案虽然解决了问题, 但调用类方法丢失了操作的便捷性, 并且如果有其他属性也要做检查时, 要重新定义这些方法, 代码将臃肿不堪. Python提供了一种装饰器的方法, 可以对类动态的添加功能. @property便可以把函数调用伪装成属性的访问, 这样既进行了参数检查, 又可以方便的调用或修改属性值.

[python] view plain copy
print ?
  1. class student(object):  
  2.  
  3.     @property  
  4.     def score(self):  
  5.         return self._score  
  6.  
  7.     @score.setter  
  8.     def score(self, score):  
  9.         if not isinstance(score, int):  
  10.             raise ValueError(‘score must be an integer’)  
  11.         if score < 0 or score > 100:  
  12.             raise ValueError(‘score must between 0~100’)  
  13.         self._score = score  
  14.   
  15. jack = student()  
  16. jack.score = 99  
  17. print jack.score  
class student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, score):
        if not isinstance(score, int):
            raise ValueError('score must be an integer')
        if score < 0 or score > 100:
            raise ValueError('score must between 0~100')
        self._score = score

jack = student()
jack.score = 99
print jack.score


四. Descriptor

        @property的方法虽然简单有效, 但它们不能被重复使用, 如果除了score需要进行参数检查, 学生年龄也许要检查, 则需要定义多个@property的方法, 代码重用效率较低. 类内代码无法做到简介漂亮. descriptor是property的升级版, 允许为重复的参数检查编写单独的类来处理. descriptor的工作流程如下:

[python] view plain copy
print ?
  1. class ParameterCheck(object):  
  2.   
  3.     def __init__(self, score):  
  4.         self.default = score  
  5.   
  6.     def __get__(self, instance, owner):  
  7.         return self.default  
  8.   
  9.     def __set__(self, instance, value):  
  10.         if not isinstance(value, int):  
  11.             raise ValueError(‘value must be an integer’)  
  12.         if value < 0 or value > 100:  
  13.             raise ValueError(‘value must between 0~100’)  
  14.         self.default = value  
  15.   
  16. class student(object):  
  17.     score = ParameterCheck(0)  
  18.     age = ParameterCheck(0)  
  19.   
  20. Jack = student()  
  21. Jack.score = 80  
  22. Jack.age = 20  
  23. print Jack.score  
  24. print Jack.age  
class ParameterCheck(object):

    def __init__(self, score):
        self.default = score

    def __get__(self, instance, owner):
        return self.default

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        self.default = value

class student(object):
    score = ParameterCheck(0)
    age = ParameterCheck(0)

Jack = student()
Jack.score = 80
Jack.age = 20
print Jack.score
print Jack.age
        ParameterCheck就是上文所谓的descripor, 其中__get__/__set__方法是描述符方法, 当解释器遇到print Jack.score时, 它就会自动把score当做一个带有__get__方法的描述符, 调用student.score.__get__方法并返回default值, 而赋值时, __set__方法的调用过程类似, 另外还存在__det__方法, 调用del Jack.age时, 自动删除属性.


五. Descripor中的代码坑

        由于描述符是以单独类的形式出现的, 所以可以有效的解决了代码@property臃肿的问题. 但不要认为descriptor如此简单, 因为Python语言本身设计的结构, 这里有几个代码坑需要注意:


1. 描述符必须在class level的位置

[python] view plain copy
print ?
  1. class ParameterCheck(object):  
  2.   
  3.     def __init__(self, score):  
  4.         self.default = score  
  5.   
  6.     def __get__(self, instance, owner):  
  7.         return self.default  
  8.   
  9.     def __set__(self, instance, value):  
  10.         if not isinstance(value, int):  
  11.             raise ValueError(‘value must be an integer’)  
  12.         if value < 0 or value > 100:  
  13.             raise ValueError(‘value must between 0~100’)  
  14.         self.default = value  
  15.   
  16.   
  17. class student(object):  
  18.   
  19.     def __init__(self):  
  20.         self.score = ParameterCheck(0)  
  21.   
  22. Jack = student()  
  23. Jack.score = 800  
  24. print “Jack.score = %s” % Jack.score  
class ParameterCheck(object):

    def __init__(self, score):
        self.default = score

    def __get__(self, instance, owner):
        return self.default

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        self.default = value


class student(object):

    def __init__(self):
        self.score = ParameterCheck(0)

Jack = student()
Jack.score = 800
print "Jack.score = %s" % Jack.score

        以上代码片将描述符放到__init__的函数中, 由打印结果看出, 程序并没有进行描述符中的参数检查. 所以描述符必须放到类的层次上才能正常工作, 不然python无法自动调用描述符中的方法.


2. 确保实例的数据只属于实例本身

        其实, Descriptor那一节为了简单起见, 描述符的内容写的过于随便, 当然在那一节中程序并没有问题, 但如果实例化两个学生的成绩 

[python] view plain copy
print ?
  1. Jack = student()  
  2. Tom = student()  
  3. Jack.score = 88  
  4. print “’”“’” * 10, “result”, ”’” * 10  
  5. print “Jack.score =”, Jack.score  
  6. Tom.score = 59  
  7. print “Jack.score =”, Jack.score  
  8.   
  9. ””””” result ”’”””‘  
  10. Jack.score = 88  
  11. Jack.score = 59  
Jack = student()
Tom = student()
Jack.score = 88
print "'" * 10, "result", "'" * 10
print "Jack.score =", Jack.score
Tom.score = 59
print "Jack.score =", Jack.score

'''''''''' result ''''''''''
Jack.score = 88
Jack.score = 59
        从运行结果可以看出, 修改实例Tom的分数会造成Jack分数的变动, 这也是Python设计让人觉得不爽的一处, 解决方法之一是使用一个字典单独保存专属于实例的数据:

[python] view plain copy
print ?
  1. from weakref import WeakKeyDictionary  
  2.   
  3. class ParameterCheckDict(object):  
  4.   
  5.     def __init__(self, score):  
  6.         self.default = score  
  7.         self.InstanceDict = WeakKeyDictionary()  
  8.   
  9.     def __get__(self, instance, owner):  
  10.         return self.InstanceDict.get(instance, self.default)  
  11.   
  12.     def __set__(self, instance, value):  
  13.         if not isinstance(value, int):  
  14.             raise ValueError(‘value must be an integer’)  
  15.         if value < 0 or value > 100:  
  16.             raise ValueError(‘value must between 0~100’)  
  17.         self.InstanceDict[instance] = value  
  18.         # print self.InstanceDict.items()  
  19.   
  20.   
  21. class student(object):  
  22.     score = ParameterCheckDict(0)  
  23.   
  24. Jack = student()  
  25. Tom = student()  
  26. Jack.score = 88  
  27. print “’”“’” * 10, “result”, ”’” * 10  
  28. print “Jack.score =”, Jack.score  
  29. Tom.score = 59  
  30. print “Jack.score =”, Jack.score  
  31.   
  32. ””””” result ”’”””‘  
  33. Jack.score = 88  
  34. Jack.score = 88  
from weakref import WeakKeyDictionary

class ParameterCheckDict(object):

    def __init__(self, score):
        self.default = score
        self.InstanceDict = WeakKeyDictionary()

    def __get__(self, instance, owner):
        return self.InstanceDict.get(instance, self.default)

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        self.InstanceDict[instance] = value
        # print self.InstanceDict.items()


class student(object):
    score = ParameterCheckDict(0)

Jack = student()
Tom = student()
Jack.score = 88
print "'" * 10, "result", "'" * 10
print "Jack.score =", Jack.score
Tom.score = 59
print "Jack.score =", Jack.score

'''''''''' result ''''''''''
Jack.score = 88
Jack.score = 88
        PS: 使用WeakKeyDictionary的目的是为了防止内存泄露.


3. 描述符的所有者为unhashable对象

        由于字典自身的特性, 以下代码段将报错:

[python] view plain copy
print ?
  1. class student(list):  
  2.     score = ParameterCheckDict(0)  
  3.   
  4. Jack = student()  
  5. print “’”“’” * 10, “result”, ”’” * 10  
  6. print Jack.score  
  7.   
  8. ””””” result ”’”””‘  
  9. TypeError: unhashable type: ’student’  
class student(list):
    score = ParameterCheckDict(0)

Jack = student()
print "'" * 10, "result", "'" * 10
print Jack.score

'''''''''' result ''''''''''
TypeError: unhashable type: 'student'
        这是因为student是list的子类, Jack实例是不可哈希的, 因此不可作为Jack.score.InstanceDict的key. 解决这个问题最好的方法是给描述符加标签:

[python] view plain copy
print ?
  1. class ParameterCheckLabel(object):  
  2.   
  3.     def __init__(self, label):  
  4.         self.label = label  
  5.   
  6.     def __get__(self, instance, owner):  
  7.         return instance.__dict__.get(self.label)  
  8.   
  9.     def __set__(self, instance, value):  
  10.         if not isinstance(value, int):  
  11.             raise ValueError(‘value must be an integer’)  
  12.         if value < 0 or value > 100:  
  13.             raise ValueError(‘value must between 0~100’)  
  14.         instance.__dict__[self.label] = value  
  15.         # print self.InstanceDict.items()  
  16.   
  17.   
  18. class student(list):  
  19.     score = ParameterCheckLabel(’score’)  
  20.   
  21. Jack = student()  
  22. Tom = student()  
  23. Jack.score = 30  
  24. print “’”“’” * 10, “result”, ”’” * 10  
  25. Tom.score = 40  
  26. print Jack.score  
class ParameterCheckLabel(object):

    def __init__(self, label):
        self.label = label

    def __get__(self, instance, owner):
        return instance.__dict__.get(self.label)

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        instance.__dict__[self.label] = value
        # print self.InstanceDict.items()


class student(list):
    score = ParameterCheckLabel('score')

Jack = student()
Tom = student()
Jack.score = 30
print "'" * 10, "result", "'" * 10
Tom.score = 40
print Jack.score
        以上代码片确实解决了字典存储的问题, 同样是字典存储, 之前的字典存储的Key是instance(例如Jack), 这里的字典存储的Key是属性值(例如score), 请求Jack.score时, 系统自动调用Jack.__dict__[‘score’], 但这段代码是特别脆弱的:

[python] view plain copy
print ?
  1. class student(list):  
  2.     score = ParameterCheckLabel(”value”)  
  3.   
  4. Jack = student()  
  5. Jack.score = 30  
  6. print “’”“’” * 10, “result”, ”’” * 10  
  7. Jack.value = 40  
  8. print Jack.score  
  9.   
  10. ””””” result ”’”””‘  
  11. 40  
class student(list):
    score = ParameterCheckLabel("value")

Jack = student()
Jack.score = 30
print "'" * 10, "result", "'" * 10
Jack.value = 40
print Jack.score

'''''''''' result ''''''''''
40

        虽然如此, 当遇到不可哈希的对象时, 这种方法还是很普遍. 由于此类问题可能造成不必要的麻烦, 可以采用元类的方法将属性的标签自动定义为属性变量:

[python] view plain copy
print ?
  1. class ParameterCheckLabel(object):  
  2.   
  3.     def __init__(self):  
  4.         self.label = None  
  5.   
  6.     def __get__(self, instance, owner):  
  7.         return instance.__dict__.get(self.label, None)  
  8.   
  9.     def __set__(self, instance, value):  
  10.         if not isinstance(value, int):  
  11.             raise ValueError(‘value must be an integer’)  
  12.         if value < 0 or value > 100:  
  13.             raise ValueError(‘value must between 0~100’)  
  14.         instance.__dict__[self.label] = value  
  15.   
  16.   
  17. class DescriptorOwner(type):  
  18.   
  19.     def __new__(cls, name, bases, attrs):  
  20.         for n, v in attrs.items():  
  21.             if isinstance(v, ParameterCheckLabel):  
  22.                 v.label = n  
  23.         return super(DescriptorOwner, cls).__new__(cls, name, bases, attrs)  
  24.   
  25.   
  26. class student(list):  
  27.     __metaclass__ = DescriptorOwner  
  28.     score = ParameterCheckLabel()  
  29.   
  30. Jack = student()  
  31. Jack.score = 30  
  32. print “’”“’” * 10, “result”, ”’” * 10  
  33. print Jack.score  
  34.   
  35. ””””” result ”’”””‘  
  36. 30  
class ParameterCheckLabel(object):

    def __init__(self):
        self.label = None

    def __get__(self, instance, owner):
        return instance.__dict__.get(self.label, None)

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        instance.__dict__[self.label] = value


class DescriptorOwner(type):

    def __new__(cls, name, bases, attrs):
        for n, v in attrs.items():
            if isinstance(v, ParameterCheckLabel):
                v.label = n
        return super(DescriptorOwner, cls).__new__(cls, name, bases, attrs)


class student(list):
    __metaclass__ = DescriptorOwner
    score = ParameterCheckLabel()

Jack = student()
Jack.score = 30
print "'" * 10, "result", "'" * 10
print Jack.score

'''''''''' result ''''''''''
30
        但这样大大增加了代码的复杂度, 要不要采用元类的方法视情况而定.


六. 属性作为字典Key__求批评指正

        对于unhashable的对象, 既然不能作为字典的key值, 那么我们换一种思路, 把实例的属性对象直接作为字典的key值, 这样不论对象是不是可哈希的, 都可以用描述符了, 也不存在上文所提到的为属性打标签的问题. 代码如下:

[python] view plain copy
print ?
  1. class ParameterCheckLabel(object):  
  2.   
  3.     def __get__(self, instance, owner):  
  4.         return instance.__dict__.get(self)  
  5.   
  6.     def __set__(self, instance, value):  
  7.         if not isinstance(value, int):  
  8.             raise ValueError(‘value must be an integer’)  
  9.         if value < 0 or value > 100:  
  10.             raise ValueError(‘value must between 0~100’)  
  11.         instance.__dict__[self] = value  
  12.   
  13.   
  14. class student(list):  
  15.     score = ParameterCheckLabel()  
  16.     age = ParameterCheckLabel()  
  17.   
  18. Jack = student()  
  19. Jack.score = 30  
  20. print “’”“’” * 10, “result”, ”’” * 10  
  21. Jack.age = 90  
  22. print “Jack.age =”, Jack.age  
  23. print “Jack.score =”, Jack.score  
  24.   
  25. ””””” result ”’”””‘  
  26. Jack.age = 90  
  27. Jack.score = 30  
class ParameterCheckLabel(object):

    def __get__(self, instance, owner):
        return instance.__dict__.get(self)

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('value must be an integer')
        if value < 0 or value > 100:
            raise ValueError('value must between 0~100')
        instance.__dict__[self] = value


class student(list):
    score = ParameterCheckLabel()
    age = ParameterCheckLabel()

Jack = student()
Jack.score = 30
print "'" * 10, "result", "'" * 10
Jack.age = 90
print "Jack.age =", Jack.age
print "Jack.score =", Jack.score

'''''''''' result ''''''''''
Jack.age = 90
Jack.score = 30

猜你喜欢

转载自blog.csdn.net/peiwang245/article/details/78360849