Just to extend on the correct answer from @chris-b, here follow some examples based on the OP use case and a pattern I have already tried (and mostly failed, at least according in terms of code beauty).
recap
As a summary, if you call super.__init__
in each and every class init you write, python will nicely follow the MRO to call all the inits for all the classes when multiple inheritance is used.
The super.__init__
works calling the parent.__init__
, and delegating the parent.__init__
to call all it's siblings inits.
It follows that, for a simple class C(A, B)
, B.__init__
will only be called if A.__init__
itself calls super.__init__
, even if C.__init__
uses super.__init__
.
The alternative is to manually call the inits that you want, eg. A.__init__(self)
and B.__init__(self)
in C.__init__
; the disadvantage is that this pattern potentially breaks future inherited classes that call super and expect all the parent inits to be called as well. One must /know/ what the various parent inits do.
One would thus think that using super.__init__
all the time is the correct thing to do; but as the OP states, this 'magic' chain of calls breaks when different arguments are expected by the different init (a common thing with a mixin pattern!).
More info can be found in How does Python's super() work with multiple inheritance?
Is there a perfect solution?
Unfortunately. it seems like in Python use of multiple inheritance (and mixin patterns) requires some knowledge of what is going on at multiple levels;
even trying to plan for extended cases by accepting *args
and **kwargs
and calling super.__init__
passing all arguments will fail because object.init() accept only one parameter (self)!
That is shown in the first example hereafter.
An extremely ugly hack I've been using and that works (albeit potentially not for all possible situations) is to wrap the calls to super.init in try except blocks such as:
try:
super(ThisClass, self).__init__(*args, arg1=arg1, arg2=arg2, **kwargs)
except TypeError as e:
# let's hope this TypeError is due to the arguments for object...
super(ThisClass, self).__init__()
That seems to work - but really ugly.
I've made a gist: https://gist.github.com/stefanocrosta/1d113a6a0c79f853c30a64afc4e8ba0a
but just in case these are the examples:
Full Example 1
class BaseClass(object):
def __init__(self, base_arg, base_arg2=None, *args, **kwargs):
print "\tBaseClass: {}, {}".format(base_arg, base_arg2)
super(BaseClass, self).__init__(*args, base_arg=base_arg, base_arg2=base_arg2, **kwargs)
class MixinClass(object):
def __init__(self, mixin_arg, *args, **kwargs):
print "\tMixinClass: {}".format(mixin_arg)
super(MixinClass, self).__init__()
class MixinClassB(object):
def __init__(self, mixin_arg, *args, **kwargs):
print "\tMixinClassB: {}".format(mixin_arg)
super(MixinClassB, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
class ChildClassA(BaseClass, MixinClass):
"""
Let's make it work for this case
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
super(ChildClassA, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)
class ChildClassB(BaseClass, MixinClass):
"""
Same as above, but without specifying the super.__init__ arguments names
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
# If you don't specify the name of the arguments, you need to use the correct order of course:
super(ChildClassB, self).__init__(base_arg, base_arg2, mixin_arg)
class ChildClassC(BaseClass, MixinClassB, MixinClass):
"""
Now let's simply add another mixin: before...
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
super(ChildClassC, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)
class ChildClassD(BaseClass, MixinClass, MixinClassB):
"""
Now let's simply add another mixin: ..and after
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
super(ChildClassD, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)
childA = ChildClassA(1, 3, 2) # note the order of the arguments - the mixin arg is interleaved
childB = ChildClassB(1, 3, 2)
childC = ChildClassC(1, 3, 2)
childD = ChildClassD(1, 3, 2)
Full Example 2:
class BaseClass(object):
def __init__(self, base_arg, base_arg2=None, *args, **kwargs):
print "\tBaseClass: {}, {}".format(base_arg, base_arg2)
try:
super(BaseClass, self).__init__(*args, base_arg=base_arg, base_arg2=base_arg2, **kwargs)
except:
super(BaseClass, self).__init__()
class MixinClass(object):
def __init__(self, mixin_arg, *args, **kwargs):
print "\tMixinClass: {}".format(mixin_arg)
try:
super(MixinClass, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
except:
super(MixinClass, self).__init__()
class MixinClassB(object):
def __init__(self, mixin_arg, *args, **kwargs):
print "\tMixinClassB: {}".format(mixin_arg)
try:
super(MixinClassB, self).__init__(*args, mixin_arg=mixin_arg, **kwargs)
except:
super(MixinClassB, self).__init__()
class ChildClassA(BaseClass, MixinClass):
"""
Let's make it work for this case
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
super(ChildClassA, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)
class ChildClassC(BaseClass, MixinClassB, MixinClass):
"""
Now let's simply add another mixin: before...
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
super(ChildClassC, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)
class ChildClassD(BaseClass, MixinClass, MixinClassB):
"""
Now let's simply add another mixin: ..and after
"""
def __init__(self, base_arg, mixin_arg, base_arg2=None):
print "Initializing {}: base_arg: {} mixin_arg: {} base_arg2: {}".format(
self.__class__.__name__, base_arg, mixin_arg, base_arg2)
super(ChildClassD, self).__init__(base_arg=base_arg, mixin_arg=mixin_arg, base_arg2=base_arg2)
try:
base = BaseClass(1, 2)
except Exception as e:
print "Failed because object.__init__ does not expect any argument ({})".format(e)
childA = ChildClassA(1, 3, 2) # note the order of the arguments - the mixin arg is interleaved
childC = ChildClassC(1, 3, 2)
childD = ChildClassD(1, 3, 2)