2

Multiple Inheritance in Python calling parent methods

class A:
    def m(self):
        print(f"m of A called")
class B(A):
    def m(self):
        print("m of B called")
        super().m()
class C(A):
    def m(self):
        print("m of C called")
        super().m()
class D(B,C):
    def m(self):
        print("m of D called")
        super().m()

x = D()
x.m()

Gives the output

m of D called

m of B called

m of C called

m of A called

Why is m of A only called once? My assumption is that it should be called twice, once by B() and once by C().

I was expecting the A.m() method to be called and printed out twice, but it only shows up once.

sbooyah
  • 23
  • 4
  • 2
    Your code is a common example of [diamond inheritance problem](https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem) and the linearization logic(C3 Method Resolution Order) used by Python to handle this ambiguity is explained really well here: https://www.python.org/download/releases/2.3/mro/ – Ashwini Chaudhary May 31 '23 at 05:51
  • Does this answer your question? [What does "mro()" do?](https://stackoverflow.com/questions/2010692/what-does-mro-do) – matszwecja May 31 '23 at 07:27

1 Answers1

3

This is the MRO concept of python, I think this article is helpful: GFG

In your case, the mro is D -> B -> C -> A. Because B and C both use same parent, Python first checks the left class(B), then the class after that(C), then their common parent(A).

You can always check the mro by yourself by:

...
print(D.__mro__)
...
Amin
  • 2,605
  • 2
  • 7
  • 15
  • 2
    +1. A key observation to make from this behavior is that you should avoid this pattern if possible. See `import this` for more details :) – flakes May 31 '23 at 05:52
  • 1
    @flakes I wouldn't necessarily agree. For example, mixins are a case where such behaviour can be used for the benefit of the code. I also don't see anything in Python Zen directly calling this out. – matszwecja May 31 '23 at 07:30
  • @matszwecja mixins are fine, especially when defined without a constructor. Complicated method resolution ordering when multiple classes within the tree define the same method is what I'm suggesting to avoid. – flakes May 31 '23 at 10:43
  • @flakes But that's one of the ways to achieve mixins; each having same method that calls its `super()` will ensure all of those methods are called. – matszwecja May 31 '23 at 11:13
  • @matszwecja Typically I find that mixins add functionality to the class without affecting the core behavior, and act mostly as a way to compartmentalize re-usable logic. I would find it unusual to see multiple mixins on a class that would duplicate added methods, complicating your MRO. I have no issue with `super()` or multiple inheritance for that pattern-- My issue is when you have multiple resolvers to a `super()` call which sit on the same level of an inheritance tree, making the ordering of invocations dependent on ordering of parent classes in the subclass definition. – flakes May 31 '23 at 11:28
  • @flakes if you have mixins that together work to effect programming *hooks*, say `def on_save_validate_data`, then a given type of use case may very well rely on what *you* find to be an antipattern. Not to say a quick heuristic on a design is not to wonder at complex mro chaining, but there are valid enough reasons, *if one knows what one is doing.*. Reading a noob’s code replete with it? Yes, probably a red flag. – JL Peyret May 31 '23 at 16:36