8

Hello awesome community, I was learning the OOPS concepts with python as a part of my curriculum. I am having a problem with multiple inheritance in python. The following is my code:

#!/usr/bin/env python

class Base1(object):
    def __init__(self):
        self.base1var = "this is base1"


class Base2(object):
    def __init__(self):
        self.base2var = "this is base2"

class MainClass(Base1, Base2):
    def __init__(self):
        super(MainClass, self).__init__()


if __name__ == "__main__":
    a = MainClass()
    print a.base1var
    print a.base2var

and when running, I am getting the following error

print a.base2var
AttributeError: 'MainClass' object has no attribute 'base2var'

If I swap the order of classes inherited, the variable name in the error changes accordingly.

Am I using super() wrong when I want to call the constructors of the two inherited classes?

How can I correctly inherit from multiple base classes and use the variables and methods in the main class without this error?

Thank you.

file2cable
  • 564
  • 6
  • 15

5 Answers5

8

You need to add a super call to Base1 so that Base2's __init__ will get called after Base1's. You can also add a super call to Base2. It's not necessary, but it won't hurt.

class Base1(object):
    def __init__(self):
        super(Base1, self).__init__()
        self.base1var = "this is base1"

class Base2(object):
    def __init__(self):
        #super(Base2, self).__init__()
        self.base2var = "this is base2"

class MainClass(Base1, Base2):
    def __init__(self):
        super(MainClass, self).__init__()


if __name__ == "__main__":
    a = MainClass()
    print a.base1var
    print a.base2var

output

this is base1
this is base2

BTW, you really should be using Python 3. super is much nicer in modern Python. :)

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • 2
    Arguably, `super` is easier to understand in Python 2; since you can't dispense with the arguments, you are forced (at least a little) to contemplate *why* the particular arguments are necessary. Python 3 could lead one to wonder why `super` is a callable at all, rather than just a keyword referring to the current class. (It's undeniably more *convenient* in Python 3 for the vast majority of use cases, though.) – chepner Jan 03 '18 at 18:34
  • @chepner I guess so. `super` in Python 3 may be a little _too_ magical. :) – PM 2Ring Jan 03 '18 at 18:36
4

If you use super(), you must use it consistently to ensure all classes' methods are called. Your Base1 and Base2 classes should also call super(...).__init__() in their __init__() methods. This will ensure that all classes in the method resolution order are eventually called.

This does get a little tricky when not all __init__() methods have an empty argument list. At some point, super() will have you calling object.__init__(), and that never takes any arguments. The trouble is, you might not know which classes this might happen in ahead of time. You can remedy this by creating an object subclass that takes any number of arguments in __init__(), and deriving from that. That class needn't call object.__init__() because that method doesn't actually do anything (or, if you're paranoid that that might change, you can call object.__init__() without using super()).

In general, super() requires extra care in argument handling because you might not know which class will be next in the method resolution order, since users of your code might make new classes using your base classes in combinations you haven't anticipated. So you'll need to do some work to make sure the method signatures in all your classes are compatible.

For more information, see Raymond Hettinger's seminal Python's Super() Considered Super.

kindall
  • 178,883
  • 35
  • 278
  • 309
2

Basically what is happening is that Python is looking through the parent classes (and their parents, etc. See the link at the end of this paragraph) for some method __init__. It reads the classes left to right so it sees Base1 first. Since Base1.__init__ exists, that method is called. Read a more detailed explanation at this answer.

I believe the only way to call all of the appropriate methods is to do so manually

class MainClass(Base1, Base2):
    def __init__(self):
        Base1.__init__(self)
        Base2.__init__(self)

then

a = MainClass()
print(a.base1var)
print(a.base2var)

prints

this is base1
this is base2
Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
  • 1
    Not the parent classes; it is picking the next method in the MRO, which may include classes not known statically to `MainClass`. – chepner Jan 03 '18 at 18:26
1

Base1 __init__ overrides Base2 __init__.py so base2var is never created.

You should use super, see How does Python's super() work with multiple inheritance?

Romain Cata
  • 141
  • 1
  • 2
0

It is not mistake to use the super but not for that goal.Explained super I have changed your code

#!/usr/bin/env python

class Base1(object):
    def __init__(self):
        self.base1var = "this is base1"


class Base2(object):
    def __init__(self):
        self.base2var = "this is base2"

class MainClass(Base2, Base1):
    def __init__(self):
        super(MainClass, self).__init__()


if __name__ == "__main__":
    a = MainClass()
    print (a.base2var)

Than you have

this is base2
Richard Rublev
  • 7,718
  • 16
  • 77
  • 121