Calling parent class __init__ with multiple inheritance, what's the right way?
Solution 1
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.
Solution 2
The answer to your question depends on one very important aspect: Are your base classes designed for multiple inheritance?
There are 3 different scenarios:
-
The base classes are unrelated, standalone classes.
If your base classes are separate entities that are capable of functioning independently and they don't know each other, they're not designed for multiple inheritance. Example:
class Foo: def __init__(self): self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar
Important: Notice that neither
Foo
norBar
callssuper().__init__()
! This is why your code didn't work correctly. Because of the way diamond inheritance works in python, classes whose base class isobject
should not callsuper().__init__()
. As you've noticed, doing so would break multiple inheritance because you end up calling another class's__init__
rather thanobject.__init__()
. (Disclaimer: Avoidingsuper().__init__()
inobject
-subclasses is my personal recommendation and by no means an agreed-upon consensus in the python community. Some people prefer to usesuper
in every class, arguing that you can always write an adapter if the class doesn't behave as you expect.)This also means that you should never write a class that inherits from
object
and doesn't have an__init__
method. Not defining a__init__
method at all has the same effect as callingsuper().__init__()
. If your class inherits directly fromobject
, make sure to add an empty constructor like so:class Base(object): def __init__(self): pass
Anyway, in this situation, you will have to call each parent constructor manually. There are two ways to do this:
-
Without
super
class FooBar(Foo, Bar): def __init__(self, bar='bar'): Foo.__init__(self) # explicit calls without super Bar.__init__(self, bar)
-
With
super
class FooBar(Foo, Bar): def __init__(self, bar='bar'): super().__init__() # this calls all constructors up to Foo super(Foo, self).__init__(bar) # this calls all constructors after Foo up # to Bar
Each of these two methods has its own advantages and disadvantages. If you use
super
, your class will support dependency injection. On the other hand, it's easier to make mistakes. For example if you change the order ofFoo
andBar
(likeclass FooBar(Bar, Foo)
), you'd have to update thesuper
calls to match. Withoutsuper
you don't have to worry about this, and the code is much more readable. -
-
One of the classes is a mixin.
A mixin is a class that's designed to be used with multiple inheritance. This means we don't have to call both parent constructors manually, because the mixin will automatically call the 2nd constructor for us. Since we only have to call a single constructor this time, we can do so with
super
to avoid having to hard-code the parent class's name.Example:
class FooMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # forwards all unused arguments self.foo = 'foo' class Bar: def __init__(self, bar): self.bar = bar class FooBar(FooMixin, Bar): def __init__(self, bar='bar'): super().__init__(bar) # a single call is enough to invoke # all parent constructors # NOTE: `FooMixin.__init__(self, bar)` would also work, but isn't # recommended because we don't want to hard-code the parent class.
The important details here are:
- The mixin calls
super().__init__()
and passes through any arguments it receives. - The subclass inherits from the mixin first:
class FooBar(FooMixin, Bar)
. If the order of the base classes is wrong, the mixin's constructor will never be called.
- The mixin calls
-
All base classes are designed for cooperative inheritance.
Classes designed for cooperative inheritance are a lot like mixins: They pass through all unused arguments to the next class. Like before, we just have to call
super().__init__()
and all parent constructors will be chain-called.Example:
class CoopFoo: def __init__(self, **kwargs): super().__init__(**kwargs) # forwards all unused arguments self.foo = 'foo' class CoopBar: def __init__(self, bar, **kwargs): super().__init__(**kwargs) # forwards all unused arguments self.bar = bar class CoopFooBar(CoopFoo, CoopBar): def __init__(self, bar='bar'): super().__init__(bar=bar) # pass all arguments on as keyword # arguments to avoid problems with # positional arguments and the order # of the parent classes
In this case, the order of the parent classes doesn't matter. We might as well inherit from
CoopBar
first, and the code would still work the same. But that's only true because all arguments are passed as keyword arguments. Using positional arguments would make it easy to get the order of the arguments wrong, so it's customary for cooperative classes to accept only keyword arguments.This is also an exception to the rule I mentioned earlier: Both
CoopFoo
andCoopBar
inherit fromobject
, but they still callsuper().__init__()
. If they didn't, there would be no cooperative inheritance.
Bottom line: The correct implementation depends on the classes you're inheriting from.
The constructor is part of a class's public interface. If the class is designed as a mixin or for cooperative inheritance, that must be documented. If the docs don't mention anything of the sort, it's safe to assume that the class isn't designed for cooperative multiple inheritance.
Solution 3
Either approach ("new style" or "old style") will work if you have control over the source code for A
and B
. Otherwise, use of an adapter class might be necessary.
Source code accessible: Correct use of "new style"
class A(object):
def __init__(self):
print("-> A")
super(A, self).__init__()
print("<- A")
class B(object):
def __init__(self):
print("-> B")
super(B, self).__init__()
print("<- B")
class C(A, B):
def __init__(self):
print("-> C")
# Use super here, instead of explicit calls to __init__
super(C, self).__init__()
print("<- C")
>>> C()
-> C
-> A
-> B
<- B
<- A
<- C
Here, method resolution order (MRO) dictates the following:
-
C(A, B)
dictatesA
first, thenB
. MRO isC -> A -> B -> object
. -
super(A, self).__init__()
continues along the MRO chain initiated inC.__init__
toB.__init__
. -
super(B, self).__init__()
continues along the MRO chain initiated inC.__init__
toobject.__init__
.
You could say that this case is designed for multiple inheritance.
Source code accessible: Correct use of "old style"
class A(object):
def __init__(self):
print("-> A")
print("<- A")
class B(object):
def __init__(self):
print("-> B")
# Don't use super here.
print("<- B")
class C(A, B):
def __init__(self):
print("-> C")
A.__init__(self)
B.__init__(self)
print("<- C")
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C
Here, MRO does not matter, since A.__init__
and B.__init__
are called explicitly. class C(B, A):
would work just as well.
Although this case is not "designed" for multiple inheritance in the new style as the previous one was, multiple inheritance is still possible.
Now, what if A
and B
are from a third party library - i.e., you have no control over the source code for A
and B
? The short answer: You must design an adapter class that implements the necessary super
calls, then use an empty class to define the MRO (see Raymond Hettinger's article on super
- especially the section, "How to Incorporate a Non-cooperative Class").
Third-party parents: A
does not implement super
; B
does
class A(object):
def __init__(self):
print("-> A")
print("<- A")
class B(object):
def __init__(self):
print("-> B")
super(B, self).__init__()
print("<- B")
class Adapter(object):
def __init__(self):
print("-> C")
A.__init__(self)
super(Adapter, self).__init__()
print("<- C")
class C(Adapter, B):
pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C
Class Adapter
implements super
so that C
can define the MRO, which comes into play when super(Adapter, self).__init__()
is executed.
And what if it's the other way around?
Third-party parents: A
implements super
; B
does not
class A(object):
def __init__(self):
print("-> A")
super(A, self).__init__()
print("<- A")
class B(object):
def __init__(self):
print("-> B")
print("<- B")
class Adapter(object):
def __init__(self):
print("-> C")
super(Adapter, self).__init__()
B.__init__(self)
print("<- C")
class C(Adapter, A):
pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C
Same pattern here, except the order of execution is switched in Adapter.__init__
; super
call first, then explicit call. Notice that each case with third-party parents requires a unique adapter class.
So it seems that unless I know/control the init's of the classes I inherit from (
A
andB
) I cannot make a safe choice for the class I'm writing (C
).
Although you can handle the cases where you don't control the source code of A
and B
by using an adapter class, it is true that you must know how the init's of the parent classes implement super
(if at all) in order to do so.
Solution 4
As Raymond said in his answer, a direct call to A.__init__
and B.__init__
works fine, and your code would be readable.
However, it does not use the inheritance link between C
and those classes. Exploiting that link gives you more consistancy and make eventual refactorings easier and less error-prone. An example of how to do that:
class C(A, B):
def __init__(self):
print("entering c")
for base_class in C.__bases__: # (A, B)
base_class.__init__(self)
print("leaving c")
Solution 5
This article helps to explain cooperative multiple inheritance:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
It mentions the useful method mro()
that shows you the method resolution order. In your 2nd example, where you call super
in A
, the super
call continues on in MRO. The next class in the order is B
, this is why B
's init is called the first time.
Here's a more technical article from the official python site:
Related videos on Youtube
Comments
-
Adam Parkin almost 2 years
Say I have a multiple inheritance scenario:
class A(object): # code for A here class B(object): # code for B here class C(A, B): def __init__(self): # What's the right code to write here to ensure # A.__init__ and B.__init__ get called?
There's two typical approaches to writing
C
's__init__
:- (old-style)
ParentClass.__init__(self)
- (newer-style)
super(DerivedClass, self).__init__()
However, in either case, if the parent classes (
A
andB
) don't follow the same convention, then the code will not work correctly (some may be missed, or get called multiple times).So what's the correct way again? It's easy to say "just be consistent, follow one or the other", but if
A
orB
are from a 3rd party library, what then? Is there an approach that can ensure that all parent class constructors get called (and in the correct order, and only once)?Edit: to see what I mean, if I do:
class A(object): def __init__(self): print("Entering A") super(A, self).__init__() print("Leaving A") class B(object): def __init__(self): print("Entering B") super(B, self).__init__() print("Leaving B") class C(A, B): def __init__(self): print("Entering C") A.__init__(self) B.__init__(self) print("Leaving C")
Then I get:
Entering C Entering A Entering B Leaving B Leaving A Entering B Leaving B Leaving C
Note that
B
's init gets called twice. If I do:class A(object): def __init__(self): print("Entering A") print("Leaving A") class B(object): def __init__(self): print("Entering B") super(B, self).__init__() print("Leaving B") class C(A, B): def __init__(self): print("Entering C") super(C, self).__init__() print("Leaving C")
Then I get:
Entering C Entering A Leaving A Leaving C
Note that
B
's init never gets called. So it seems that unless I know/control the init's of the classes I inherit from (A
andB
) I cannot make a safe choice for the class I'm writing (C
).-
Stevoisiak almost 6 years
- (old-style)
-
Adam Parkin about 12 yearsNo, they don't. If B's init doesn't call super, then B's init will not be called if we do the
super().__init__()
approach. If I callA.__init__()
andB.__init__()
directly, then (if A and B do callsuper
) I get B's init being called multiple times. -
Raymond Hettinger about 12 years@AdamParkin To use super() with multiple inheritance, the classes need to be designed for cooperative multiple inheritance using the principles outlined in the the article. Your other choice is to let C call both A and B's code directly.
-
Raymond Hettinger about 12 years@AdamParkin (regarding your question as edited): If one of the parent classes isn't designed for use with super(), it can usually be wrapped in a way that adds the super call. The referenced article shows a worked-out example in the section titled "How to Incorporate a Non-cooperative Class".
-
Ben about 12 years@RaymondHettinger Well, you already wrote and linked to an article with some thoughts about that in your answer, so I don't think I have much to add to that. :) I don't think it's possible to generically adapt any non-super-using class to a super-hierarchy though; you have to come up with a solution tailored to the particular classes involved.
-
Adam Parkin about 12 yearsSomehow I managed to miss that section when I read the article. Exactly what I was looking for. Thanks!
-
Adam Parkin about 12 yearsOf course, the adapter approach means you now need to replicate the interface of the class you're inheriting from in the adapter. Ick, but safe.
-
Shawn Mehan almost 7 yearsIf you are writing python (hopefully 3!) and using inheritance of any sort, but especially multiple, then rhettinger.wordpress.com/2011/05/26/super-considered-super should be required reading.
-
Stephen Ellwood over 5 yearsThe best answer imho. Found this particularly helpful as it is more futureproof
-
Nicholas Hamilton over 4 yearsSo what if you want to pass take two parameters into C, and pass one parameter to A, and pass the other to B?
-
msouth over 4 yearsUpvoting because we finally know why we don't have flying cars when we were sure we would have by now.
-
Minix over 4 yearsYour second point blew me away. I only ever saw Mixins to the right of the actual super class and thought they were pretty loose and dangerous, since you can't check if the class you are mixing into has the attributes you are expecting it to have. I never thought about putting a general
super().__init__(*args, **kwargs)
into the mixin and writing it first. It makes so much sense. -
Shambhav Agrawal over 3 yearsIn Source code accessible: Correct use of "new style", How is -> B & <- B getting printed. If I comment "super(A, self).__init__()" , then it wont print "->B" & "<- B". & if I comment "super(B, self).__init__()" , then everything remains same. Whats actually happening???
-
Nathaniel Jones over 3 years@ShambhavAgrawal As stated in the section you mentioned, the MRO for this example is
C -> A -> B -> object
. Whenever a call tosuper()
is made, execution is passed up the MRO chain. Sosuper(A, self).__init__()
passes off toB.__init__()
(which prints "-> B" & "<- B") andsuper(B, self).__init__()
passes off toobject.__init__()
(which doesn't print anything). -
András Aszódi over 3 yearsSimple and straight to the point. Works with Python 3 in 2020 as well -- it's indeed future-proof :-)
-
Nairum about 2 yearsAuthor already knows this.The question is how to call every child's class
__init__
.