I am trying to understand MRO in Python. Although there are various posts here, I am not particularly getting what I want. Consider two classes A
and B
derived from BaseClass
, each having an __init__
taking different params.
class BaseClass(object):
def __init__(self):
print "I am the base class"
class A(BaseClass):
def __init__(self, something, anotherthing):
super(A, self).__init__()
self.something = something
self.anotherthing = anotherthing
def methodsA(self):
...
class B(BaseClass):
def __init__(self, someOtherThing):
super(B, self).__init__()
self.someOtherThing = someOtherThing
def methodsB(self):
...
The question is, if I need to derive a Third Class C
from both A
and B
, how do I initialise the __init__
, if I have to? I can safely derive C
from either B
or A
.
class C(A,B):
def __init__(self, something, anotherthing, someOtherThing):
super(C, self).__init__(something, anotherthing, someOtherThing)
The above implementation gives me an error.
As jonrsharpe mentioned at the end of his post, the best way I've come across for handling this type of situation is accepting
**kwargs
and extracting named arguments explicitly.Arguments that need to be extracted should be passed in named, so they appear in kwargs.
You can even explicitly accept only named arguments by ommitting the
*args
as in the example, so you catch yourself with a TypeError if you forget.EDIT:
After thinking on it a while I realize that my example is very specific to your example, and if you introduce another class or change inheritance it may break. There are two things that should be addressed to make this more general:
BaseClass does not call super.
For the example this doesn't matter, but if another class is introduced the MRO might change such that there is a class after BaseClass and it should therefore call super. This leads to the second issue:
object.__init__()
takes no parametersIf we want to make the classes (BaseClass specifically) safe to put into a generic multiple inheritance structure where its super call might be dispatched to another class or object, we need to pop arguments off kwargs when we consume them.
This adds another complication, though, in that it requires that no two
__init__
functions share the same parameter name. I guess the takeaway is that making multiple inheritance work in a general way is difficult.Here is an interesting article (found through google) about some of the details: article
While bj0's answer is mostly right, manually extracting the arguments from
kwargs
is more complicated and awkward than is necessary. It also means that you won't detect when extra arguments are passed in to one of the class constructors.The best solution is to accept
**kwargs
, but only use it to pass on any unknown arguments. When this reachesobject
(BaseClass
's base), it will raise an error if there were unnecessary arguments:If one of your classes modifies (or provides a default for) an argument that is also used by one of its base classes, you can both take a specific parameter and pass it on by keyword:
You do need to be calling all your constructors with only keyword arguments, no positional ones. For instance:
It's possible to support something similar for positional arguments, but usually its a bad idea because you can't count on the argument order. If you had just one class that took positional arguments (perhaps an unknown number of them in
*args
), you could probably make that work by passing*args
into and out of each__init__
method, but multiple classes taking different positional arguments is asking for trouble due to the order they appear in possibly changing as you do multiple inheritance.Thanks all for helping me understand MRO. Below is my complete Code together with output. I hope this will also help other's.
class BaseClass(object):
class FirstGenDerived(BaseClass):
class SecondGenDerived(BaseClass):
class ThirdGenDerived(FirstGenDerived, SecondGenDerived):
if name == "main":
Output: Executing BaseClass I am called from BaseClass Robin Executing Instance of BaseClass with SetName
I am called from BaseClass I am called from BaseClass Little John
Executing FirstGenDerived with printName and printFullName
I am called from FirstDerivedClass Robin Hood I am called from FirstDerivedClass, although I was present in BaseClass His Highness Robin Hood
Executing FirstGenderived with instance
I am called from BaseClass I am called from FirstDerivedClass Edwards Hood
Executing SecondGenDerived with printName and printWholeName
I am called from SecondDerivedClass Robin Williams Hood I am called from SecondDerivedClass, although I was present in BaseClass Sir Robin Williams Hood
Executing ThirdGenDerived
I am called from BaseClass and invoked from Third Generation Derived Class I am called from FirstDerivedClass, although I was present in BaseClass His Highness Robin Hood
To understand this, try without any arguments:
When we run this:
This is what
super
does: it makes sure everything gets called, in the right order, without duplication.C
inherits fromA
andB
, so both of their__init__
methods should get called, and they both inherit fromBaseClass
, so that__init__
should also be called, but only once.If the
__init__
methods being called take different arguments, and can't deal with extra arguments (e.g.*args, **kwargs
), you get theTypeError
s you refer to. To fix this, you need to make sure that all the methods can handle the appropriate arguments.I believe you can't use
super
for this. You'll have to use the "old style":