子类中重写属性设置器的特殊性

2 投票
4 回答
1474 浏览
提问于 2025-04-17 13:02

我有这个代码:

class Parent(object):

    def __init__(self, val):
        print 'enter Base init'
        self._set_x(val)
        print 'leave Base init'

    def _get_x(self):
        return self._x

    def _set_x(self, val):
        print 'enter Base _set_x'
        self._x = val
        print 'leave Base _set_x'

    x = property(_get_x, _set_x)

class Child(Parent):

    def _set_x(self, val):
        print 'enter Child _set_x'
        y = val * 2
        super(Child, self)._set_x(y)
        print 'leave Child _set_x'

child = Child(5)
num = child.x
child.x = 5
print num == child.x

当我运行它的时候,得到的是这个结果:

enter Base init
enter Child _set_x
enter Base _set_x
leave Base _set_x
leave Child _set_x
leave Base init
enter Base _set_x
leave Base _set_x
False

我查了一些资料,有人说重写(overriding)应该不生效,但我想知道为什么会出现这种看起来不一致的情况?在初始化的时候,子类的设置方法(setter)被调用了,但当你之后对已经初始化的对象进行操作时,却调用了父类的设置方法。有人能解释一下这是怎么回事吗?

4 个回答

2
x = property(_get_x, _set_x)

在这里,x 变成了一个属性,它有一个获取值的方法 Parent._get_x 和一个设置值的方法 Parent._set_x

如果你在 Child 类里加上这一行 x = property(Parent._get_x, _set_x),那么这个属性就会被重新定义,并且会按照你预期的方式工作。

3

这里没有什么不一致的地方。__init__ 方法明确地通过属性查找调用了 self._set_x。这里的 self 指的是一个 Child 对象,而 Child 类里定义了 _set_x,并且在这个对象的方法解析顺序(MRO)中,Child 类排在第一位,所以调用的就是 _set_xChild 版本。

不过,x 属性是在 Parent 类里定义的。此时还没有涉及到子类,所以传给 property_set_x_get_x 是在 Parent 类里定义的版本。现在,当访问 Childx 属性时,Python 首先会在 Child 类里查找 x。但它没有找到,因为 Child 并没有定义这个属性。接着,它会去查找 MRO 中的下一个类:Parent。在 Parent 里找到了 x,于是就使用了在 Parent 中定义的版本。

5

因为你调用它的方式不一致——有一次是直接调用,有一次是通过属性调用。把 self._set_x(x)Parent.__init__ 中改成 self.x = x,你会发现 Child._set_x 根本没有被调用。要在子类中重写一个设置器,你可以用 property.setter 作为装饰器:

class Child(Parent):
    @Parent.x.setter
    def x(self, arg):
        super()._set_x(arg)

或者给属性增加一个间接层:

class Parent(object):
    # ...
    x = property(
        lambda self:    self._get_x(),
        lambda self, x: self._set_x(x)
    )

直接重写是行不通的,因为属性会存储具体的方法对象(Parent._get_xParent._set_x),即使对象的类型发生了变化,它也不会再去查找这些方法——运行的代码是一样的。间接调用会强制在 self 的动态类型中查找,这样就可以实现重写。

撰写回答