8.8 子类中扩展property

    考虑如下的代码,它定义了一个property:

    下面是一个示例类,它继承自Person并扩展了 属性的功能:

    1. class SubPerson(Person):
    2. @property
    3. def name(self):
    4. print('Getting name')
    5. return super().name
    6.  
    7. @name.setter
    8. def name(self, value):
    9. print('Setting name to', value)
    10. super(SubPerson, SubPerson).name.__set__(self, value)
    11.  
    12. @name.deleter
    13. def name(self):
    14. print('Deleting name')
    15. super(SubPerson, SubPerson).name.__delete__(self)

    接下来使用这个新类:

    1. >>> s = SubPerson('Guido')
    2. Setting name to Guido
    3. Getting name
    4. 'Guido'
    5. >>> s.name = 'Larry'
    6. Setting name to Larry
    7. >>> s.name = 42
    8. Traceback (most recent call last):
    9. File "<stdin>", line 1, in <module>
    10. File "example.py", line 16, in name
    11. raise TypeError('Expected a string')
    12. TypeError: Expected a string
    13. >>>

    如果你仅仅只想扩展property的某一个方法,那么可以像下面这样写:

    1. class SubPerson(Person):
    2. @Person.name.setter
    3. def name(self, value):
    4. print('Setting name to', value)
    5. super(SubPerson, SubPerson).name.__set__(self, value)

    在子类中扩展一个property可能会引起很多不易察觉的问题,因为一个property其实是 gettersetterdeleter 方法的集合,而不是单个方法。因此,当你扩展一个property的时候,你需要先确定你是否要重新定义所有的方法还是说只修改其中某一个。

    在第一个例子中,所有的property方法都被重新定义。在每一个方法中,使用了 来调用父类的实现。在 setter 函数中使用 super(SubPerson, SubPerson).name.set(self, value) 的语句是没有错的。为了委托给之前定义的setter方法,需要将控制权传递给之前定义的name属性的 set() 方法。不过,获取这个方法的唯一途径是使用类变量而不是实例变量来访问它。这也是为什么我们要使用 的原因。

    如果你只想重定义其中一个方法,那只使用 本身是不够的。比如,下面的代码就无法工作:

    1. class SubPerson(Person):
    2. @property # Doesn't work
    3. def name(self):
    4. print('Getting name')

    如果你试着运行会发现setter函数整个消失了:

    1. class SubPerson(Person):
    2. @Person.name.getter
    3. def name(self):
    4. print('Getting name')
    5. return super().name

    这么写后,property之前已经定义过的方法会被复制过来,而getter函数被替换。然后它就能按照期望的工作了:

    1. >>> s = SubPerson('Guido')
    2. >>> s.name
    3. Getting name
    4. 'Guido'
    5. >>> s.name = 'Larry'
    6. >>> s.name
    7. Getting name
    8. 'Larry'
    9. >>> s.name = 42
    10. Traceback (most recent call last):
    11. File "<stdin>", line 1, in <module>
    12. File "example.py", line 16, in name
    13. raise TypeError('Expected a string')
    14. TypeError: Expected a string
    15. >>>

    在这个特别的解决方案中,我们没办法使用更加通用的方式去替换硬编码的 Person 类名。如果你不知道到底是哪个基类定义了property,那你只能通过重新定义所有property并使用 super() 来将控制权传递给前面的实现。

    值的注意的是上面演示的第一种技术还可以被用来扩展一个描述器(在8.9小节我们有专门的介绍)。比如:

    最后值的注意的是,读到这里时,你应该会发现子类化 setter 和 方法其实是很简单的。这里演示的解决方案同样适用,但是在 Python的issue页面报告的一个bug,或许会使得将来的Python版本中出现一个更加简洁的方法。