0

I am trying to develop a class structure. I ran into problems trying to access attributes of parent classes (i.e. when I inherited BaseX and BaseY, the attributes of BaseX appeared in child class but not of BaseY) trying to inherit parent classes and went through different posts here and finally came to below structure which is similar to what I am doing with my actual code; I have been more experimental with the structure below though just to understand concepts. The actual code starts with a single parent class, then multiple layer 1 child classes inherit it, then there are further layer 2 child classes which are less than the number of layer 1 child classes and then I merge all layer 2 child classes in to one class. Now after a lot of R&D, I found a code below which is doing what I want (can access all attributes in class SubABC) but I am not sure if this is correct as despite achieving this, I am not clear on how exactly this super() is working here. And I would like to be clear with what I am doing before proceeding. For example, why single super() is sufficient in MidA and MidB but requires three super() in MidC. And why SubABC requires only one super(). I would appreciate some guidance here before I replicate this in to my code. I am no Python expert so please do point me in right direction if I need to read something - i.e. documentation on super(). Also, PyCharm (2019.3.1 - Community Edition) complains about MidA super().init(x_var, z_var) and MidB super().init(x_var, z_var) statements saying 'unexpected arguments'; not sure if it is just a bug. Thanks.

class BaseX(object):
    def __init__(self, x_var):
        print('++++ Class BaseX')
        self.x_var = x_var
        print('---- Class BaseX')


class BaseY(object):
    def __init__(self):
        print('++++ Class BaseY')
        self.y_var = 'Y_Var'
        print('---- Class BaseY')


class BaseZ(object):
    def __init__(self, z_var):
        print('++++ Class BaseZ')
        self.z_var = z_var
        print('---- Class BaseZ')


class MidA(BaseX, BaseY, BaseZ):
    def __init__(self, x_var, z_var):
        print('++++ Class MidA')
        super().__init__(x_var, z_var)
        print('---- Class MidA')

    def mid_a_func(self):
        print(self.x_var)
        print(self.y_var)
        print(self.z_var)


class MidB(BaseX, BaseY, BaseZ):
    def __init__(self, x_var, z_var):
        print('++++ Class MidB')
        super().__init__(x_var, z_var)
        print('---- Class MidB')

    def mid_b_func(self):
        print(self.x_var)
        print(self.y_var)
        print(self.z_var)


class MidC(BaseX, BaseY, BaseZ):
    def __init__(self, x_var, z_var):
        print('++++ Class MidC')
        super().__init__(x_var)
        super(BaseX, self).__init__()
        super(BaseY, self).__init__(z_var)
        print('---- Class MidC')

    def mid_b_func(self):
        print(self.x_var)
        print(self.y_var)
        print(self.z_var)


class SubABC(MidA, MidB, MidC):
    def __init__(self, x_var, z_var):
        print('++++ Class SubABC')
        super().__init__(x_var, z_var)
        print('---- Class SubABC')

    def sub_ab_func(self):
        self.mid_a_func()

ab = SubABC('X_Var', 'Z_Var')

ab.sub_ab_func()

The output is:

++++ Class SubABC
++++ Class MidA
++++ Class MidB
++++ Class MidC
++++ Class BaseX
---- Class BaseX
++++ Class BaseY
---- Class BaseY
++++ Class BaseZ
---- Class BaseZ
---- Class MidC
---- Class MidB
---- Class MidA
---- Class SubABC
X_Var
Y_Var
Z_Var
N Joshi
  • 1
  • 2
  • Does this answer your question? [python multiple inheritance passing arguments to constructors using super](https://stackoverflow.com/questions/34884567/python-multiple-inheritance-passing-arguments-to-constructors-using-super) – Booboo Jan 14 '20 at 19:32

1 Answers1

0

The reason you're getting this unexpected behaviour is that super in Python when called with no arguments returns an instance of the next class in the inheritance tree.

Consider the following example (the argument lvl refers to an indent-level so we can more clearly see the hierarchy):

class Base:
    def __init__(self, lvl):
        print(lvl, "+ Base")
        print(lvl, "- Base")

class Mid(Base):
    def __init__(self, lvl):
        print(lvl, "+ Mid")
        super().__init__(lvl + "\t")
        print(lvl, "- Mid")

class Top(Mid):
    def __init__(self):
        print(" + Top")
        super().__init__("\t")
        print(" - Top")

t = Top()

Let's build an 'inheritance tree':

- Top
    - Mid
        - Base

Drawn as a single line, it can be represented with this:

Top -> Mid -> Base

So, in Top, super() returns an instance of the next item, so of Mid. Then in Mid, it returns an instance of Base.

Unsurprisingly, we get this output:

 + Top
     + Mid
         + Base
         - Base
     - Mid
 - Top

Interestingly, we could change the code to this without any change in the output:

class Base:
    def __init__(self, lvl):
        print(lvl, "+ Base")
        print(lvl, "- Base")

class Mid:
    def __init__(self, lvl):
        print(lvl, "+ Mid")
        super().__init__(lvl + "\t")
        print(lvl, "- Mid")

class Top(Mid, Base):
    def __init__(self):
        print(" + Top")
        super().__init__("\t")
        print(" - Top")

t = Top()

because this tree is

- Top
    - Mid
    - Base

which still becomes

Top -> Mid -> Base

Now let's at the look at the 'tree' for your example:


- SubABC
    - MidA
        - BaseX
        - BaseY
        - BaseZ
    - MidB
        - BaseX
        - BaseY
        - BaseZ
    - MidC
        - BaseX
        - BaseY
        - BaseZ

This turns into

SubABC -> MidA -> MidB -> MidC -> BaseX -> BaseY -> BaseZ

So, super() in SubABC goes to MidA, and in MidA to MidB, etc.

Thus, when we get to BaseX, we reach as far as we can go. BaseX does not have a call to super(), so BaseY is never executed from it. Thus, we have to call BaseY and BaseZ explicitly.

If we apply the indent-level thing to your code, we get this output:


    ++++ Class SubABC
         ++++ Class MidA
             ++++ Class MidB
                 ++++ Class MidC
                     ++++ Class BaseX
                     ---- Class BaseX
                     ++++ Class BaseX
                     ---- Class BaseX
                     ++++ Class BaseY
                     ---- Class BaseY
                     ++++ Class BaseZ
                     ---- Class BaseZ
                 ---- Class MidC
             ---- Class MidB
         ---- Class MidA
    ---- Class SubABC

And the reason you have to use super(BaseX, self).__init__() in MidC, not super(BaseY, self).__init__() is because you need to call the method of the next parent (BaseY), and the only way to get to this is to run super() as though you were in a method of BaseX.

Hope this helps.

Ed Ward
  • 2,333
  • 2
  • 10
  • 16