python multiple inheritance passing arguments to constructors using super
Well, when dealing with multiple inheritance in general, your base classes (unfortunately) should be designed for multiple inheritance. Classes B
and C
in your example aren't, and thus you couldn't find a proper way to apply super
in D
.
One of the common ways of designing your base classes for multiple inheritance, is for the middle-level base classes to accept extra args in their __init__
method, which they are not intending to use, and pass them along to their super
call.
Here's one way to do it in python:
class A(object):
def __init__(self,a):
self.a=a
class B(A):
def __init__(self,b,**kw):
self.b=b
super(B,self).__init__(**kw)
class C(A):
def __init__(self,c,**kw):
self.c=c
super(C,self).__init__(**kw)
class D(B,C):
def __init__(self,a,b,c,d):
super(D,self).__init__(a=a,b=b,c=c)
self.d=d
This can be viewed as disappointing, but that's just the way it is.
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'>)
Python Multiple Inheritance super().__init__()
This can't be done just using super()
calls, because the superclass of the last class in the chain will be object
, which doesn't take a my_param
parameter.
See python multiple inheritance passing arguments to constructors using super for more discussion of parameter passing to __init__()
methods with multiple inheritance.
So you need to change X
to call the superclass init methods explicitly, rather than using super()
. And A
and B
shouldn't call super().__init__()
because they're subclasses of object
, which doesn't do anything in its __init__()
.
class A:
def __init__(self, my_param):
self.my_param = my_param
class B:
def __init__(self, my_param):
self.my_param = my_param * 2
class X(A, B):
def __init__(self, my_param):
A.__init__(self, my_param=my_param)
B.__init__(self, my_param=my_param)
Multiple inheritance and passing arguments to inherited class in python
I changed the class defintion of D
as follows and it worked
class D(B, C, A):
def __init__(self, a, b):
super(D, self).__init__(a=a, b=b)
So the overall class will look like, (I removed the methods to keep the code short)
class A(object):
def __init__(self, a, b):
self.a = a
self.b = b
self.c = ""
class B(A):
def __init__(self, **kw):
super(B, self).__init__(**kw)
self.x = 0
class C(A):
def __init__(self, **kw):
super(C, self).__init__(**kw)
self.c = ""
self.y = 1
class D(B, C, A):
def __init__(self, a, b):
super(D, self).__init__(a=a, b=b)
c = D(1, 2)
print(c.a, c.b, c.x, c.c, c.y)
The output will be
1 2 0 1
This is due to a rule in the MRO algorithm which says (More details in this answer here, but the gist is
a class always appears before its ancestor ("monotonicity")
Hence B and C needs to appear before A, since A is the ancestor of B and C
Or in other words:
D is inheriting from A,B and C. Because B and C already inherits from A, there is no way python now cannot determine what class to look methods up on first; either A, or B and C, if you use the old order of defining. D(A, B, C)
Multiple inheritance in python3 with different signatures
Do not use super(baseclass, ...)
unless you know what you are doing. The first argument to super()
tells it what class to skip when looking for the next method to use. E.g. super(A, ...)
will look at the MRO, find A
, then start looking for __init__
on the next baseclass, not A
itself. For C
, the MRO is (C, A, B, object)
, so super(A, self).__init__
will find B.__init__
.
For these cases, you don't want to use cooperative inheritance but directly reference A.__init__
and B.__init__
instead. super()
should only be used if the methods you are calling have the same signature or will swallow unsupported arguments with *args
and **vargs
. In that case just the one super(C, self).__init__()
call would be needed and the next class in the MRO order would take care of chaining on the call.
Putting it differently: when you use super()
, you can not know what class will be next in the MRO, so that class better support the arguments you pass to it. If that isn't the case, do not use super()
.
Calling the base __init__
methods directly:
class A(object):
def __init__(self, a, b):
print('Init {} with arguments {}'.format(self.__class__.__name__, (a, b)))
class B(object):
def __init__(self, q):
print('Init {} with arguments {}'.format(self.__class__.__name__, (q)))
class C(A, B):
def __init__(self):
# Unbound functions, so pass in self explicitly
A.__init__(self, 1, 2)
B.__init__(self, 3)
Using cooperative super()
:
class A(object):
def __init__(self, a=None, b=None, *args, **kwargs):
super().__init__(*args, **kwargs)
print('Init {} with arguments {}'.format(self.__class__.__name__, (a, b)))
class B(object):
def __init__(self, q=None, *args, **kwargs):
super().__init__(*args, **kwargs)
print('Init {} with arguments {}'.format(self.__class__.__name__, (q)))
class C(A, B):
def __init__(self):
super().__init__(a=1, b=2, q=3)
Multiple Inheritance with kwargs
Since the method resolution order is
(__main__.C, __main__.A, __main__.B, object)
, couldclass B
be defined in the following way instead?
No, because then this would fail:
class D:
def __init__(self, d, **kwargs):
self.d = d
super().__init__(**kwargs)
class E(C, D):
def __init__(self, e, **kwargs):
self.e = e
super().__init__(**kwargs)
The MRO of E
is (E, C, A, B, D, object)
, so B
must call super().__init__
otherwise D.__init__
won't be called.
Isn't
super().__init__(**kwargs)
inclass B
redundant, since any surpluskwargs
passed toC
will be passed toobject
, raising?
No, because the surplus kwargs
will go to D.__init__
in the above example. But even without that, it is not redundant to raise an error when you call a constructor with too many arguments; it is desirable to have an error message informing you about your incorrect code, rather than for the mistake to go undetected.
Is this a safeguard for if
C
was defined asclass C(B, A)
instead ofclass C(A, B)
?
In some sense, sure; but really it's a safeguard for B
occurring in any class hierarchy, so long as the other classes in the hierarchy follow the same rule of calling super().__init__(**kwargs)
.
Multiple Inheritance and calling super()
First, you have to look at the method resolution order of Top
:
>>> for c in Top.__mro__: print c
...
<class '__main__.Top'>
<class '__main__.Middle1'>
<class '__main__.Middle2'>
<class '__main__.Middle3'>
<class '__main__.Base'>
<type 'object'>
This helps you see which class each call to super
represents.
Your mistake is thinking that the call to super(Middle1, self)
refers to the (only) base class Base
of Middle1
. It does not: it refers to the the class following Middle1
in the MRO of self.__class__
. Since self.__class__
is Top
, the next class in line is Middle2
, whose __init__
takes only one argument.
To use super
correctly from a method, you need to ensure that the method takes the same arguments in every class, because you cannot predict which class's method will be called by looking at the code itself; it depends entirely on the type of the object that initiates the chain of calls, which might be a class you aren't even aware of yet.
There are two posts I suggest reading:
- Python's super() considered super!
- Python's Super is nifty, but you can't use it very carefully to understand the semantics of
super
.
Together, they give you a good understanding of when super
can be used correctly and how to avoid the problem you see here.
(In full disclosure, I haven't read either post recently, so I will refrain from trying to summarize the advice presented in each.)
How to pass arguments to parent class with inheritance and with super keyword?
The point of inheritance is to indicate that a certain class (ex. Dog
) has an "is a" relationship with a parent class (ex. Animal
). So, a Dog
is an Animal
but with certain properties that only dogs have (or appropriate only to dogs).
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
a_dog = Dog("woolfie", "labrador")
Here, the __init__
still takes a name
, but also takes in extra attributes specific for a dog, such as breed
. For the common attribute name
, that's where you can just pass it along to the parent class with super
.
So, applying that to your code, I'm assuming a BioData_OP
object is a type of BioData
, but with added age
and address
attributes. The __init__
should probably still accept the same first name and last name, but then just have added parameters for age
and address
.
class BioData:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
class BioData_OP(BioData):
def __init__(self, first_name, last_name, age, address):
super().__init__(first_name, last_name)
self.age = age
self.address = address
mydata = BioData_OP("Big","Bee", 36,"CNGF")
As recommended in the comments, depending on your intended use-case or situation, if this going to be part of a factory design pattern, you should make sure not to change the base class' initialization, but instead, let your subclass accept optional values for subclass-specific values or provide a separate method for setting those values.
class BioData:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
class BioData_OP(BioData):
def __init__(self, first_name, last_name, age=-1, address=None):
super().__init__(first_name, last_name)
self.age = age
self.address = address
def update(self, age, address):
self.age = age
self.address = address
# Still works
# Will create a BioData_OP object but without age and address
# Basically creates a BioData object
mydata_1 = BioData_OP("Big","Bee")
# Directly create a BioData_OP object
mydata_2 = BioData_OP("Big","Bee", 36,"CNGF")
# First, create a BioData object
# Then, turn in into a BioData_OP object
mydata_3 = BioData_OP("Big","Bee")
mydata_3.update(36, "CNGF")
Related Topics
Checking If Object on Ftp Server Is File or Directory Using Python and Ftplib
Interact with Other Programs Using Python
What Is the Meaning of "Failed Building Wheel for X" in Pip Install
What Do All the Distributions Available in Scipy.Stats Look Like
How to Convert a Scikit-Learn Dataset to a Pandas Dataset
How to Make Sessions Timeout in Flask
How to Initialize the Base (Super) Class
How to Read One Single Line of CSV Data in Python
Get First Row Value of a Given Column
Numpy to Tfrecords: Is There a More Simple Way to Handle Batch Inputs from Tfrecords
Set Up Python Simplehttpserver on Windows
Write() Versus Writelines() and Concatenated Strings
How to Pip or Easy_Install Tkinter on Windows
Best Way to Parse a Url Query String