子类中重写属性设置器的特殊性
我有这个代码:
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 个回答
x = property(_get_x, _set_x)
在这里,x
变成了一个属性,它有一个获取值的方法 Parent._get_x
和一个设置值的方法 Parent._set_x
。
如果你在 Child
类里加上这一行 x = property(Parent._get_x, _set_x)
,那么这个属性就会被重新定义,并且会按照你预期的方式工作。
这里没有什么不一致的地方。__init__
方法明确地通过属性查找调用了 self._set_x
。这里的 self
指的是一个 Child
对象,而 Child
类里定义了 _set_x
,并且在这个对象的方法解析顺序(MRO)中,Child
类排在第一位,所以调用的就是 _set_x
的 Child
版本。
不过,x
属性是在 Parent
类里定义的。此时还没有涉及到子类,所以传给 property
的 _set_x
和 _get_x
是在 Parent
类里定义的版本。现在,当访问 Child
的 x
属性时,Python 首先会在 Child
类里查找 x
。但它没有找到,因为 Child
并没有定义这个属性。接着,它会去查找 MRO 中的下一个类:Parent
。在 Parent
里找到了 x
,于是就使用了在 Parent
中定义的版本。
因为你调用它的方式不一致——有一次是直接调用,有一次是通过属性调用。把 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_x
和 Parent._set_x
),即使对象的类型发生了变化,它也不会再去查找这些方法——运行的代码是一样的。间接调用会强制在 self
的动态类型中查找,这样就可以实现重写。