1

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.

BHC
  • 889
  • 1
  • 7
  • 18
  • 1
    one benefit to use super is code maintainability. Once name of A and B changed, your code will break – galaxyan Feb 07 '19 at 18:22
  • Fair point - could you demonstrate how one would implement super with argument passing for this exact case? – BHC Feb 07 '19 at 18:24
  • Seeing as the parent classes have to be explicitly declared in the inheriting class, surely the code will still break if I change the class names. The advantage is I can change two fewer lines in init? – BHC Feb 08 '19 at 09:45
  • You don't *have* to use `super`; in fact, not doing so is a legitimate choice. Not all classes need to support cooperative multiple inheritance. The point of `super` is not to avoid having to type a base class's name; it's to implement a particular model of multiple inheritance. – chepner Apr 04 '19 at 14:46

2 Answers2

1
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
galaxyan
  • 5,944
  • 2
  • 19
  • 43
  • Is this really the preferred method of implementation? C now has to be initialised with explicit knowledge of the arguments required by its parent classes. – BHC Feb 08 '19 at 10:23
  • Returning back to this post after some time away and I want to implement this again, but I still can't believe that this is the preferred method. A user who wanted to instantiate C would have absolutely no way to know what arguments it should be passed. – BHC Apr 04 '19 at 14:21
  • @BHC That's the cost of re-using `A` and `B` in the first place. What do you expect `C()` to do? – chepner Apr 04 '19 at 14:30
  • In instantiating C I would expect to be able to see the arguments that are required, not just `*args, **kwargs`. – BHC Apr 04 '19 at 14:32
  • `A` and `B` each need to remove their respective argument from the call to `super().__init__`, to avoid `object.__init__` from being called with unexpected arguments. – chepner Apr 04 '19 at 14:32
  • I understand that, but what I am trying to point out is that because we used super instead of directly instantiating the parent classes we now have a much less usable inheriting class. – BHC Apr 04 '19 at 14:35
1

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.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • `C2` looks promising, but on instantiation gives `TypeError: __init__() takes 2 positional arguments but 3 were given` – BHC Apr 04 '19 at 14:55
  • Fixed. While it's OK to add a positional argument to a overridden function, it's best to stick with keyword arguments for calling the function. (Mainly because it's the only way to accept unexpected arguments in the first place.) Note that `C2` doesn't know for a fact that `super()` refers to `A`, just as `A` in this example doesn't realize that its call to `super()` references `B` rather than `object` for an instance of `C2`. – chepner Apr 04 '19 at 14:59
  • `super` effectively shields you from caring about the run-time type of `self` when calling an upstream method. – chepner Apr 04 '19 at 15:02
  • Nice! I had wondered if the solution lay in supplying additional kwargs, but hadn't thought of removing them at each stage like this. This does exactly what I want and is a great model for using super while supplying parameters to parent classes. – BHC Apr 04 '19 at 15:21