Search code examples
pythonpython-3.xinheritanceparentmultiple-inheritance

How do I call an indirect parent class's method from a child class in Python?


I have classes A, B, and C. C inherits both from A and B, and for the most part wherever there are methods overriding each other I want A's to override B's, so I have the following structure:

class A:
    def some_method(self):
        return self.some_attribute

class B:
    def some_method(self):
        return self.some_other_attribute

class C(A,B):
    def some_method(self):
        var = super().some_method() -> this will give me the method from A, but I want the B one
        return var + 1

So my question is how can I invoke with super() or some other way the some_method() from B, so I can use it in the method override in C?

Also for the record I am using Python 3.*


Solution

  • Python is a language with multiple inheritance, which means that a class can have several direct superclasses. But for the purposes of calling super(), Python always linearizes the inheritance hierarchy to determine who the "best" superclass is. This is called the method resolution order, accessed via the __mro__ variable in Python.

    So if we have the class hierarchy

    class A(object): pass
    class B(A): pass
    class C(A): pass
    class D(B, C): pass
    

    Then although D has two superclassses, if we say super() in D, it'll only give us B. That's because the MRO for D creates a linear hierarchy for us. In Python 3, it uses an algorithm called the C3 resolution, which gives us this hierarchy

    >>> D.__mro__
    (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
    

    So if we call super() in a method on class D, we'll get the version on B, and if we call super() from there, we'll get the C version, then the A version, and finally the object one.

    So in your case, C inherits from A and B (in that order), so it's going to be biased toward A. That is, the MRO for the class C in your example is (C, A, B, object). If you want it to always go for B and then A, then simply switch the order of the superclasses.

    class C(B, A):
      ...
    

    This is the best solution. It's predictable and Python programmers will immediately understand the intention of a super() call in this class. I recommend this if it fits your needs.

    However, if you have a complicated multi-inheritance system and need access to both the A and B superclasses at the same time, you can always call them directly with a class name, forgoing the super() call altogether.

    class C(B, A):
      def some_method(self):
        A.some_method(self) # Calls A's version on self
        B.some_method(self) # Calls B's version on self
    

    If some_method takes some arguments, you can pass them after the explicit self argument. This is going to raise more eyebrows, so if you find yourself needing access to two distinct superclass methods in complicated ways, you might consider refactoring your code to use less multiple inheritance. But the feature is there if you really need it.