How Does Python'S Super() Work With Multiple Inheritance

How does Python's super() work with multiple inheritance?

This is detailed with a reasonable amount of detail by Guido himself in his blog post Method Resolution Order (including two earlier attempts).

In your example, Third() will call First.__init__. Python looks for each attribute in the class's parents as they are listed left to right. In this case, we are looking for __init__. So, if you define

class Third(First, Second):
...

Python will start by looking at First, and, if First doesn't have the attribute, then it will look at Second.

This situation becomes more complex when inheritance starts crossing paths (for example if First inherited from Second). Read the link above for more details, but, in a nutshell, Python will try to maintain the order in which each class appears on the inheritance list, starting with the child class itself.

So, for instance, if you had:

class First(object):
def __init__(self):
print "first"

class Second(First):
def __init__(self):
print "second"

class Third(First):
def __init__(self):
print "third"

class Fourth(Second, Third):
def __init__(self):
super(Fourth, self).__init__()
print "that's it"

the MRO would be [Fourth, Second, Third, First].

By the way: if Python cannot find a coherent method resolution order, it'll raise an exception, instead of falling back to behavior which might surprise the user.

Example of an ambiguous MRO:

class First(object):
def __init__(self):
print "first"

class Second(First):
def __init__(self):
print "second"

class Third(First, Second):
def __init__(self):
print "third"

Should Third's MRO be [First, Second] or [Second, First]? There's no obvious expectation, and Python will raise an error:

TypeError: Error when calling the metaclass bases
Cannot create a consistent method resolution order (MRO) for bases Second, First

Why do the examples above lack super() calls? The point of the examples is to show how the MRO is constructed. They are not intended to print "first\nsecond\third" or whatever. You can – and should, of course, play around with the example, add super() calls, see what happens, and gain a deeper understanding of Python's inheritance model. But my goal here is to keep it simple and show how the MRO is built. And it is built as I explained:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
<class '__main__.Second'>, <class '__main__.Third'>,
<class '__main__.First'>,
<type 'object'>)

Calling parent class __init__ with multiple inheritance, what's the right way?

Both ways work fine. The approach using super() leads to greater flexibility for subclasses.

In the direct call approach, C.__init__ can call both A.__init__ and B.__init__.

When using super(), the classes need to be designed for cooperative multiple inheritance where C calls super, which invokes A's code which will also call super which invokes B's code. See http://rhettinger.wordpress.com/2011/05/26/super-considered-super for more detail on what can be done with super.

[Response question as later edited]

So it seems that unless I know/control the init's of the classes I
inherit from (A and B) I cannot make a safe choice for the class I'm
writing (C).

The referenced article shows how to handle this situation by adding a wrapper class around A and B. There is a worked-out example in the section titled "How to Incorporate a Non-cooperative Class".

One might wish that multiple inheritance were easier, letting you effortlessly compose Car and Airplane classes to get a FlyingCar, but the reality is that separately designed components often need adapters or wrappers before fitting together as seamlessly as we would like :-)

One other thought: if you're unhappy with composing functionality using multiple inheritance, you can use composition for complete control over which methods get called on which occasions.

Using super() with multiple inheritance

When you create an instance of C, it's better if you pass keyword arguments so that you know which is which. Positional arguments work too but with keyword arguments, you don't have to worry about messing up the order of arguments.

Then since C defers the definition of self.name and self.age to parent classes, you immediately pass all arguments up the heirarchy using super().__init__(**kwargs). The best thing about inheritance is that child classes don't even need to know about the parameters it doesn't handle. The first parent class is A, so self.name is defined there. The next parent class in the method resolution order is B, so from A, you pass the remaining keyword arguments to B, using super().__init__(**kwargs) yet again.

class A(object):
def __init__(self, name, **kwargs):
self.name = name
super().__init__(**kwargs)

class B(object):
def __init__(self, age, **kwargs):
self.age = age
super().__init__(**kwargs) # this is actually not needed but in case you have other base classes after B, might come in handy

class C(A,B):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def display(self):
print(self.name, self.age)

c = C(name="xyz",age=12)
c.display()

Output:

xyz 12

Multiple inheritance with super() in Python

The link from @Thierry Lathuille is the correct one to read, but I'll try to add some extra explanation. The MRO for the initializer is [Boss, Worrior, Archer, Player]. What this means (somewhat confusingly) is that when Worrior calls super(), this is actually referring to Archer, not Player. If you place print(super().__init__) before each time you call the method, you will find output like this prior to your crash:

<bound method Worrior.__init__ of <__main__.Boss object at 0x10af5f780>>
<bound method Archer.__init__ of <__main__.Boss object at 0x10af5f780>>
Traceback (most recent call last):
...

This is the major pitfall IMHO with multiple inheritance in Python, and I generally advise against it unless you have zero-argument initializers.

If you try to explicitly call each base class initializer (e.g. Player.__init__), then you will end up with some of your initializers executing multiple times.

In order to be able to pass your arguments through, you'll need to leverage **kwargs. Additionally, as these get passed through, each unique argument name will get stripped from the kwargs. So this means that you can't reuse name as an initialization argument. I'll use worrior_name and archer_name instead. So putting this all together, the following will run each initializer only once, without crashing:

class Player:
def __init__(self, hp, **kwargs):
print(super().__init__)
self.hp = hp

def sign_in(self):
print('player sign in')


class Worrior(Player):
def __init__(self, worrior_name, power, **kwargs):
super().__init__(**kwargs)
self.name = worrior_name
self.power = power

def __str__(self):
return "The worrior's name: " f'{self.name} \n' \
"He has the power:" f'{self.power}'


class Archer(Player):
def __init__(self, archer_name, agility, **kwargs):
super().__init__(**kwargs)
self.name = archer_name
self.agility = agility

def __str__(self):
return "The archer's name: " f'{self.name} \n' \
"He has the agility:" f'{self.agility}'


class Boss(Worrior, Archer):
def __init__(self, name, power, agility, hp):
super().__init__(archer_name=name, worrior_name=name, power=power, agility=agility, hp=hp)

def __str__(self):
return "The boss's name: " f'{self.name} \n' \
"With Worrior's power " f'{self.power} \n' \
"and With Archer's agilit" f'{self.agility}' \
"The boss' hp is: " f'{self.hp}'

Python's Multiple Inheritance: Picking which super() to call

That's not what super() is for. Super basically picks one (or all) of its parents in a specific order. If you only want to call a single parent's method, do this

class ASDF(ASDF1, ASDF2, ASDF3):
def __init__(self):
ASDF2.__init__(self)

Python super().__init__() with multiple inheritances

Super function only calls the parent class from MRO order. Acc to this, your prefered class would be School and the init of School will be called. You have to give only school_name as parameter while calling super().__init__(self,school_name). But if you want to call particular __init__(), then it is better to use <parent_class>.<method>(<parameters>). If you want to call both the init functions, try doing this:

class School:
def __init__(self, school_name):
self._school = school_name

class Exam:
def __init__(self, exam_name):
self._exam_name = exam_name
def credit(self):
return 3

class Test(School, Exam):
def __init__(self, school_name, exam_name):
self._date = "Oct 7"
School.__init__(self,school_name)
Exam.__init__(self,exam_name)

test = Test('Success', 'Math')
print(test._school)
print(test._exam_name)

Try doing this

Inheriting form two classes in python with super. can you call parent init methods with super()?

A child has a mother and a father and would best be designed to have mother and father attributes contained within. I think the following class definitions make more sense for your case:

class Person():
def __init__(self, name):
self._name = name

def get_name(self):
return self._name

class Child(Person):
def __init__(self, name, father_name, mother_name):
super().__init__(name)
self._father = Person(father_name)
self._mother = Person(mother_name)

def get_father_name(self):
return self._father.get_name()

def get_mother_name(self):
return self._mother.get_name()


child = Child('Peter', 'Joseph', 'Mary')
print(child.get_name(), child.get_father_name(), child.get_mother_name())

Prints:

Peter Joseph Mary


Related Topics



Leave a reply



Submit