I have three classes: A, B and C.
C inherits from A and B (in this order). The constructor signatures of A and B are different. How can I call the __init__ methods of both parent classes?
My endeavour in code:
class A(object):
def __init__(self, a, b):
super(A, self).__init__()
print('Init {} with arguments {}'.format(self.__class__.__name__, (a, b)))
class B(object):
def __init__(self, q):
super(B, self).__init__()
print('Init {} with arguments {}'.format(self.__class__.__name__, (q)))
class C(A, B):
def __init__(self):
super(A, self).__init__(1, 2)
super(B, self).__init__(3)
c = C()
yields the error:
Traceback (most recent call last):
File "test.py", line 16, in <module>
c = C()
File "test.py", line 13, in __init__
super(A, self).__init__(1, 2)
TypeError: __init__() takes 2 positional arguments but 3 were given
I found this resource which explains mutiple inheritance with different set of arguments, but they suggest to use *args and **kwargs to use for all argument. I consider this very ugly, since I cannot see from the constructor call in the child class what kind of parameters I pass to the parent classes.
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)
Related
Consider the following snippet of python code
class A(object):
def __init__(self, a):
self.a = a
class B(A):
def __init__(self, a, b):
super(B, self).__init__(a)
self.b = b
class C(A):
def __init__(self, a, c):
super(C, self).__init__(a)
self.c = c
class D(B, C):
def __init__(self, a, b, c, d):
#super(D,self).__init__(a, b, c) ???
self.d = d
I am wondering how can I pass a, b and c to corresponding base classes' constructors.
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.
Unfortunately, there is no way to make this work using super() without changing the Base classes. Any call to the constructors for B or C is going to try and call the next class in the Method Resolution Order, which will always be B or C instead of the A class that the B and C class constructors assume.
The alternative is to call the constructors explicitly without the use of super() in each class.
class A(object):
def __init__(self, a):
object.__init__()
self.a = a
class B(A):
def __init__(self, a, b):
A.__init__(self, a)
self.b = b
class C(A):
def __init__(self, a, c):
A.__init__(self, a)
self.c = c
class D(B, C):
def __init__(self, a, b, c, d):
B.__init__(self, a, b)
C.__init__(self, a, c)
self.d = d
There is still a downside here as the A constructor would be called twice, which doesn't really have much of an effect in this example, but can cause issues in more complex constructors. You can include a check to prevent the constructor from running more than once.
class A(object):
def __init__(self, a):
if hasattr(self, 'a'):
return
# Normal constructor.
Some would call this a shortcoming of super(), and it is in some sense, but it's also just a shortcoming of multiple inheritance in general. Diamond inheritance patterns are often prone to errors. And a lot of the workarounds for them lead to even more confusing and error-prone code. Sometimes, the best answer is to try and refactor your code to use less multiple inheritance.
A key concept: super does not refer to the parent class. It refers to the next class in the mro list, which depends on the actual class being instantiated.
So when calling super().__init__, the actual method called is undetermined from the calling frame.
That's why the classes have to be specially designed for mixin.
Even a class witch inherits only from object, should call super().__init__.
And of course, when object__init__(**kwargs) is called, kwargs should be empty by then; else case an error will raise.
Example:
class AMix:
def __init__(self, a, **kwargs):
super().__init__(**kwargs)
self.a = a
class BMix:
def __init__(self, b, **kwargs):
super().__init__(**kwargs)
self.b = b
class AB(AMix, BMix):
def __init__(self, a, b):
super().__init__(a=a, b=b)
ab = AB('a1', 'b2')
print(ab.a, ab.b) # -> a1 b2
I was not completely satisfied with the answers here, because sometimes it gets quite handy to call super() for each of the base classes separately with different parameters without restructuring them. Hence, I created a package called multinherit and you can easily solve this issue with the package. https://github.com/DovaX/multinherit
from multinherit.multinherit import multi_super
class A(object):
def __init__(self, a):
self.a = a
print(self.a)
class B(A):
def __init__(self, a, b):
multi_super(A,self,a=a)
self.b = b
print(self.b)
class C(A):
def __init__(self, a, c):
multi_super(A,self,a=a)
self.c = c
print(self.c)
class D(B, C):
def __init__(self, a, b, c, d):
multi_super(B,self,a=a,b=b)
multi_super(C,self,a=a,c=c)
self.d = d
print(self.d)
print()
print("d3")
d3=D(1,2,3,4)
print(d3._classes_initialized)
>>> d3
>>> 1
>>> 2
>>> 3
>>> 4
>>> [<class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>]
I am trying to design a class structure that allows the user to define their own class that overloads predefined methods in other classes. In this case the user would create the C class to overload the "function" method in D. The user created C class has common logic for other user created classes A and B so they inherit from C to overload "function" but also inherit from D to use D's other methods. The issue I am having is how to pass "value" from A and B to D and ignore passing it to C. What I currently have written will produce an error as C does not have "value" as an argument.
I know that I can add "value" (or *args) to C's init method and the super call but I don't want to have to know what inputs other classes need in order to add new classes to A and B. Also, if I swap the order of C and D I won't get an error but then I don't use C's overloaded "function". Is there an obvious way around this?
class D(SomethingElse):
def __init__(self, value, **kwargs):
super(D, self).__init__(**kwargs)
self.value = value
def function(self):
return self.value
def other_method(self):
pass
class C(object):
def __init__(self):
super(C, self).__init__()
def function(self):
return self.value*2
class B(C, D):
def __init__(self, value, **kwargs):
super(B, self).__init__(value, **kwargs)
class A(C, D):
def __init__(self, value, **kwargs):
super(A, self).__init__(value, **kwargs)
a = A(3)
print(a.function())
>>> 6
Essentially, there are two things you need to do to make your __init__ methods play nice with multiple inheritance in Python:
Always take a **kwargs parameter, and always call super().__init__(**kwargs), even if you think you are the base class. Just because your superclass is object doesn't mean you are last (before object) in the method resolution order.
Don't pass your parent class's __init__ arguments explicitly; only pass them via **kwargs. Your parent class isn't necessarily the next one after you in the method resolution order, so positional arguments might be passed to the wrong other __init__ method.
This is called "co-operative subclassing". Let's try with your example code:
class D:
def __init__(self, value, **kwargs):
self.value = value
super().__init__(**kwargs)
def function(self):
return self.value
class C:
# add **kwargs parameter
def __init__(self, **kwargs):
# pass kwargs to super().__init__
super().__init__(**kwargs)
def function(self):
return self.value * 2
class B(C, D):
# don't take parent class's value arg explicitly
def __init__(self, **kwargs):
# pass value arg via kwargs
super().__init__(**kwargs)
class A(C, D):
# don't take parent class's value arg explicitly
def __init__(self, **kwargs):
# pass value arg via kwargs
super().__init__(**kwargs)
Demo:
>>> a = A(value=3)
>>> a.value
3
>>> a.function()
6
Note that value must be passed to the A constructor as a keyword argument, not as a positional argument. It's also recommended to set self.value = value before calling super().__init__.
I've also simplified class C(object): to class C:, and super(C, self) to just super() since these are equivalent in Python 3.
So I'm trying to understand the point of A AND B. I'm guessing that maybe you want to mix in the superclass behavior and sometimes have local behavior. So suppose A is just mixing together behaviors, and B has some local behavior and state.
If you don't need your own state, you probably don't need an __init__. So for A and C just omit __init__.
class SomethingElse(object):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
class D(SomethingElse):
def __init__(self, value, *args, **kwargs):
super(D, self).__init__(*args, **kwargs)
self.value = value
def function(self):
return self.value
def other_method(self):
return self.__dict__
class C(object):
#def __init__(self):
# super(C, self).__init__()
def function(self):
return self.value*2
class B(C, D):
def __init__(self, value, bstate, *args, **kwargs):
super(B, self).__init__(value, *args, **kwargs)
self.bstate = bstate
def __repr__(self):
return (self.__class__.__name__ + ' ' +
self.bstate + ' ' + str(self.other_method()))
class A(C, D):
pass
a = A(3)
b = B(21, 'extra')
a.function()
6
b.function()
42
repr(a)
'<xx.A object at 0x107cf5e10>'
repr(b)
"B extra {'args': (), 'bstate': 'extra', 'value': 21, 'kwargs': {}}"
I've kept python2 syntax assuming you might still be using it, but as another answer points out, python3 simplifies super() syntax, and you really should be using python3 now.
If you swap C and D you are changing the python method resolution order, and that will indeed change the method to which a call to A.function resolves.
I am trying to figure how to use super() to initialize the parent class one by one based on condition.
class A:
def __init__(self, foo):
self.foo = foo
class B:
def __init__(self, bar):
self.bar == bar
class C(A,B):
def __init__(self):
#Initialize class A first.
#Do some calculation and then initialize class B
How do I use super() in class C such that it only initializes class A first, then I do some calc and call super() to initialize class B
You cannot do what you ask for in C.__init__, as super doesn't give you any control over which specific inherited methods get called, only the order in which they are called, and that is controlled entirely by the order in which the parent classes are listed.
If you use super, you need to use it consistently in all the classes. (That's why it's called cooperative inheritance.) Note this means that C cannot inject any code between the calls to A.__init__ and B.__init__.
__init__ is particularly tricky to implement correctly when using super, because a rule of super is that you have to expected arbitrary arguments to be passed, yet object.__init__() doesn't take any arguments. You need each additional argument to be "owned" by a particular root class that is responsible for removing it from the argument list.
class A:
def __init__(self, foo, **kwargs):
# A "owns" foo; pass everything else on
super().__init__(**kwargs)
self.foo = foo
class B:
def __init__(self, bar, **kwargs):
# B "owns" bar; pass everything else on
super().__init__(**kwargs)
self.bar = bar
class C(A,B):
def __init__(self):
# Must pass arguments expected by A and B
super().__init__(foo=3, bar=9)
The MRO for C is [A, B, object], so the call tree looks something like this:
C.__init__ is called with no arguments
super() resolves to A, so A.__init__ is called with foo=3 and bar=9.
In A.__init__, super() resolves to B, so B.__init__ is called with bar=9.
In B.__init__, super() resolves to object, so object.__init__ is called with no arguments (kwargs being empty)
Once object.__init__ returns, self.bar is set to bar
Once B.__init__ returns, self.foo is set to foo
Once A.__init__ returns, C.__init__ finishes up
OK, the first sentence isn't entirely true. Since neither A nor B, as currently written, use super, you might be able to assume that an appropriate use of super will simply call one parent function and immediately return.
class A:
def __init__(self, foo):
self.foo = foo
class B:
def __init__(self, bar):
self.bar == bar
class C(A,B):
def __init__(self):
super(A, self).__init__(foo=3)
# Do some calculation
super(B, self).__init__(bar=9)
I'm not entirely certain, though, that this doesn't introduce some hard-to-predict bugs that could manifest with other subclasses of A, B, and/or C that attempt to use super properly.
You can actually refer the base classes explicitly:
class A:
def __init__(self, foo):
self.foo = foo
class B:
def __init__(self, bar):
self.bar == bar
class C(A,B):
def __init__(self):
A.__init__(self, 'foovalue')
# Do some calculation
B.__init__(self, 'barvalue')
For the case of the most basic multiple inheritance:
class A:
def __init__(self, a):
self.a = a
class B:
def __init__(self, b):
self.b = b
class C(A, B):
def __init__(self, a, b):
A.__init__(self, a)
B.__init__(self, b)
I do not see why super() should be used. I suppose you could implement it with kwargs, but that is surely less readable than the above method. I am yet to find any answers on stack overflow which are in favour of this method, yet surely for this case it is the most satisfactory?
There are a lot of questions marked as duplicate on this topic, but no satisfactory answers for this exact case. This question addresses multiple inheritance and the use of super() for a diamond inheritance. In this case there is no diamond inheritance and neither parent class have any knowledge of each other, so they shouldn't need to call super() like this suggests.
This answer deals with the use of super in this scenario but without passing arguments to __init__ like is done here, and this answer deals with passing arguments but is again a diamond inheritance.
One correct way to use super here would be
class A:
def __init__(self, a, **kwargs):
super().__init__(**kwargs)
self.a = a
class B:
def __init__(self, b, **kwargs):
super().__init__(**kwargs)
self.b = b
class C1(A, B):
pass
class C2(A, B):
def __init__(self, a, b, **kwargs):
super().__init__(a=a, b=b, **kwargs)
c1 = C1(a="foo", b="bar")
c2 = C2(a="foo", b="bar")
The method resolution order for C is [C, A, B, object]. Each time super() is called, it returns a proxy for the next class in the MRO, based on where super() is called at the time.
You have two options when defining C, depending on whether you want to define C.__init__ with a signature that mentions the two arguments A and B required for initialization. With C1, C1.__init__ is not defined so A.__init__ will be called instead. With C2, you need to explicitly call the next __init__ method in the chain.
C, knowing that it is a subclass of A and B, has to at least provide the expected arguments for the known upstream __init__ methods.
A.__init__ will pass on everything except a to the next class's __init__ method.
B.__init__ will pass on everything it receives except b.
object.__init__ will finally be called, and assuming all previous classes correctly removed the keyword arguments they introduced, will receive no additional arguments.
Changing the order in which the various __init__s are called means changing the MRO, which means altering the order of the base classes. If you want more control than that, then cooperative multiple inheritance is not for you.
class A(object):
def __init__(self, *args, **kwargs):
super(A, self).__init__(*args, **kwargs)
self.a = kwargs['a']
class B(object):
def __init__(self, *args, **kwargs):
super(B, self).__init__()
self.b = kwargs['b']
class C(A, B):
def __init__(self, *args, **kwargs):
super(C, self).__init__(*args, **kwargs)
z = C(a=1,b=2)
z.b
2
In A.__init__ I call self.func(argument):
class A(object):
def __init__(self, argument, key=0):
self.func(argument)
def func(self, argument):
#some code here
I want to change the signature of A.func in B. B.func gets called in B.__init__ through A.__init__:
class B(A):
def __init__(self, argument1, argument2, key=0):
super(B, self).__init__(argument1, key) # calls A.__init__
def func(self, argument1, argument2):
#some code here
Clearly, this doesn't work because the signature of B.func expects two arguments while A.__init__ calls it with one argument. How do I work around this? Or is there something incorrect with the way I have designed my classes?
key is a default argument to A.__init__. argument2 is not intended for key. argument2 is an extra argument that B takes but A does not. B also takes key and has default value for it.
Another constraint is that I would like not to change the signature of A.__init__. key will usually be 0. So I want to allow users to be able to write A(arg) rather than A(arg, key=0).
Generally speaking, changing the signature of a method between subclasses breaks the expectation that the methods on subclasses implement the same API as those on the parent.
However, you could re-tool your A.__init__ to allow for arbitrary extra arguments, passing those on to self.func():
class A(object):
def __init__(self, argument, *extra, **kwargs):
key = kwargs.get('key', 0)
self.func(argument, *extra)
# ...
class B(A):
def __init__(self, argument1, argument2, key=0):
super(B, self).__init__(argument1, argument2, key=key)
# ...
The second argument passed to super(B, self).__init__() is then captured in the extra tuple, and applied to self.func() in addition to argument.
In Python 2, to make it possible to use extra however, you need to switch to using **kwargs, otherwise key is always going to capture the second positional argument. Make sure to pass on key from B with key=key.
In Python 3, you are not bound by this restriction; put *args before key=0 and only ever use key as a keyword argument in calls:
class A(object):
def __init__(self, argument, *extra, key=0):
self.func(argument, *extra)
I'd give func() an *extra parameter too, so that it's interface essentially is going to remain unchanged between A and B; it just ignores anything beyond the first parameter passed in for A, and beyond the first two for B:
class A(object):
# ...
def func(self, argument, *extra):
# ...
class B(A):
# ...
def func(self, argument1, argument2, *extra):
# ...
Python 2 demo:
>>> class A(object):
... def __init__(self, argument, *extra, **kwargs):
... key = kwargs.get('key', 0)
... self.func(argument, *extra)
... def func(self, argument, *extra):
... print('func({!r}, *{!r}) called'.format(argument, extra))
...
>>> class B(A):
... def __init__(self, argument1, argument2, key=0):
... super(B, self).__init__(argument1, argument2, key=key)
... def func(self, argument1, argument2, *extra):
... print('func({!r}, {!r}, *{!r}) called'.format(argument1, argument2, extra))
...
>>> A('foo')
func('foo', *()) called
<__main__.A object at 0x105f602d0>
>>> B('foo', 'bar')
func('foo', 'bar', *()) called
<__main__.B object at 0x105f4fa50>
It seems to be that there is a problem in your design. The following might fix your particular case but seems to perpetuate bad design even further. Notice the Parent.method being called directly.
>>> class Parent:
def __init__(self, a, b=None):
Parent.method(self, a)
self.b = b
def method(self, a):
self.location = id(a)
>>> class Child(Parent):
def __init__(self, a):
super().__init__(a, object())
def method(self, a, b):
self.location = id(a), id(b)
>>> test = Child(object())
Please consider adding a default argument to the second parameter of the method you are overriding. Otherwise, design your class and call structure differently. Reorganization might eliminate the problem.
actually I would resort to put an extra boolean argument in A's __init__ to control the call of the func, and just pass False from B's __init__
class A(object):
def __init__(self, argument, key=0, call_func=True):
if call_func:
self.func(argument)
class B(A):
def __init__(self, argument):
argument1, argument2 = argument, 'something else'
super(B, self).__init__(argument1, argument2, call_func=False)