Dave Dave - 6 months ago 9
Python Question

Override a field in parent class with property in child class

Where I am now looks like this:

class A(object):
def __init__(self, val):
self.x=val
self.y=42
# other fields

class B(object):
def __init__(self):
self.a=22
# other fields

class C(A,B):
def __init__(self, val):
super(C,self).__init__(val)
@property
def x(self):
# if A.x is None return a value that I can compute from A.y and B.a
# if A.x is not None return it
@x.setter
def x(self, val):
# set the field value


Sometimes I just want to set an assumed value for
x
by hand, in which case I would just use an
A
. In other cases I want to use a more complicated approach that involves computing
A.x
's value on the basis of information that is organized into a
B
. The idea in this code is to make a
C
class that can look like an
A
(in terms of the
x
field) but doesn't need that field value to be set by hand, instead it just gets derived.

What I can't figure out is how to have the
C.x
property shadow the
A.x
field in a sensible way.

Answer

The line self.x = val in the A.__init__ method will simply invoke your C.x setter. You already have everything handled here. You are handling per instance attributes here, not attributes on a class that are inherited by subclasses.

All you need to do is to set a different attribute in the setter to represent the x value. You could name it _x, for example:

class C(A, B):
    _x = None

    @property
    def x(self):
        if self._x is not None:
            return self._x
        return self.a + self.y

    @x.setter
    def x(self, val):
        self._x = val

Note that if all C.__init__ does is call super().__init__, you don't need it at all. However, you do need to make sure at least A.__init__() plays along in the inheritance structure; add in more calls to super().__init__():

class A(object):
    def __init__(self, val, *args, **kwargs):
        try:
            super(A, self).__init__(self, *args, **kwargs)
        except TypeError:
            pass
        self.x = val
        self.y = 42

class B(object):
    def __init__(self, *args, **kwargs):
        try:
            super(B, self).__init__(self, *args, **kwargs)
        except TypeError:
            pass
        self.a = 22

Using *args and **kwargs allows these methods to pass on any extra arguments to other classes in the hierarchy. By using try..except the classes remain useable anywhere in the hierarchy, even if they are the last in the chain (so object__init__() is called in the end).

Demo, using the above classes:

>>> c = C(None)
>>> c.x
64
>>> c.x = 15
>>> c.x
15