-1

I'm working on some Python code that seems like it'll be a good fit for multiple inheritance, and I'm reading about multiple inheritance and super() to make sure I correctly understand how they work.

My understanding of super() is that - to paraphrase - it walks a class's method resolution order and returns a "dispatcher" of sorts, where accessing a method or attribute via __getattr__ accesses the "first" implementation of that method or attribute, as dictated by the MRO.

However, this example from RealPython has me baffled. (I've added print statements, and I've edited their example to remove all the functional methods, since all I care about here is how super() works with the MRO and the various __init__ methods.)

class Rectangle:
    def __init__(self, length, width, **kwargs):
        print("Running Rectangle.__init__")
        self.length = length
        self.width = width
        super().__init__(**kwargs)

class Square(Rectangle):
    def __init__(self, length, **kwargs):
        print("Running Square.__init__")
        super().__init__(length=length, width=length, **kwargs)

class Triangle:
    def __init__(self, base, height, **kwargs):
        print("Running Triangle.__init__")
        self.base = base
        self.height = height
        super().__init__(**kwargs)

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height, **kwargs):
        print("Running RightPyramid.__init__")
        self.base = base
        self.slant_height = slant_height
        kwargs["height"] = slant_height
        kwargs["length"] = base
        super().__init__(base=base, **kwargs)

_ = RightPyramid(10, 5)

My expectation is that because Square comes before Triangle in RightPyramid's inheritance order, super(RightPyramid).__init__ is equivalent to Square.__init__. And indeed:

>>> super(RightPyramid).__init__
<bound method Square.__init__ of <__main__.RightPyramid object at [...]>

However, when I actually run this code to look at what gets printed:

lux@parabolica:~/Desktop $ python test.py
Running RightPyramid.__init__  # No problem
Running Square.__init__  # Square comes first, makes sense
Running Rectangle.__init__  # Square calls Rectangle, gotcha
Running Triangle.__init__  # What? How?!

What's going on here? How is Triangle.__init__ able to get called?

The funny thing is, this is actually exactly what I want to have happen in the code I'm working on - "mixing together" multiple __init__ methods from more than one superclass - but none of the documentation or articles I've read indicate that super() is supposed to behave this way. As far as I've read, super() should only "resolve to", and call, one single method; from examining super(RightPyramid).__init__, it seems like that's what's happening, but from the output of the code, Triangle.__init__ is clearly getting called... somehow.

What am I misunderstanding here, and where can I read more about this functionality of super()?

(This seems to be what the official documentation for super() is referring to in the paragraph starting with "The second use case is to support cooperative multiple inheritance...", but it doesn't go on to say anything more specific about what that means, how that works, or provide any examples for that use-case.)

4
  • 3
    What happens is that Rectangle.__init__() calls Triangle.__init__(). This is enabled by the fact that you include super().__init__() at the end of every __init__() method. Commented Nov 26, 2024 at 16:53
  • 1
    Triangle is the class after Rectangle, but before object, in _.__mro__(). Commented Nov 26, 2024 at 17:29
  • As an aside, there is no need for RightPyramid.__init__ to set the base attribute explicitly. There is one one such attribute, and Triangle.__init__ will set it when it gets called. (Unlike C++, there are not two different base attributes, one per class, that need to be carefully disambiguated.) Commented Nov 26, 2024 at 17:33
  • And in any case, a RightPyriamid has a square side and four triangle sides; it is not a kind of square or triangle. Inheritance here is not appropriate in the first place. Consider something like class RightPyramid: def __init__(self, base, height): self.base = Square(base); self.side1 = Triangle(base, ...); .... (I leave it as an exercise to derive the height of the four trianglular sides from the height of the pyramid itself.) Commented Nov 26, 2024 at 17:34

1 Answer 1

4

super().<method>() calls the method from the next class in the MRO, it doesn't call the methods in all the superclasses automatically. The entire MRO chain will only be processed if each of these methods also calls super().<method>().

This is why it's called cooperative multiple inheritance -- it depends on each class cooperating by also calling super().

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.