This weeks PyPuzzle will test your knowledge of the Method Resolution Order (MRO) and the C3 linearisation algorithm that determines the order in which classes are inherited and methods are resolved.

  • Inheritance and method resolution
  • Method Resolution Order (MRO)
  • The super() function

Feel free to use an online Python compiler and interpreter like [this] (https://www.online-python.com/) to try running the code yourself. The answer is supplied below the code.

Question

What is the expected output of the following code?

class A:
    def show(self):
        print("A")

class B(A):
    def show(self):
        print("B")
        super().show()

class C(A):
    def show(self):
        print("C")
        super().show()

class D(B, C):
    def show(self):
        print("D")
        super().show()

d = D()
d.show()

Hints

  1. Python uses the C3 linearization algorithm to determine the MRO of a class. What does the MRO look like for D?
  2. The super() function doesn’t always refer to the immediate parent class. Instead, it follows the MRO.
  3. How do multiple inheritance and super() calls interact?

Answer

The output is:

D
B
C
A

  • MRO (Method Resolution Order): The MRO for class D is determined using the C3 linearization algorithm. The MRO for D is: [D, B, C, A].
  • When d.show() is called:
    • D.show() is executed first and prints D. It then calls super().show(), which refers to the next class in the MRO, B.
    • B.show() is called and prints B. It also calls super().show(), which refers to C, the next in the MRO.
    • C.show() is executed and prints C. It calls super().show(), which moves to A.
    • A.show() is executed and prints A. Since A doesn’t have a super().show() call, the chain ends there.

Learnings

  1. Method Resolution Order (MRO): The MRO determines the order in which classes are checked when a method is called. It is calculated using the C3 linearization algorithm, which ensures that the inheritance hierarchy is respected without ambiguity. Note that the MRO is unique for each class yet the MRO of a subclass is still consistent with the MRO of its parent class.

The MRO of a class can be checked with the __mro__ attribute or the mro() method.

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

print(D.__mro__)
# Output: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

  1. C3 Linearisation Algorithm: This algorithm determines the MRO for each class. It works by merging the MROs of the direct parent classes and the list of parent classes themselves. The C3 linearisation algorithm follows the following principles:
    • Preserve the local precedence order: If a class B inherits from a class A, B should appear before A in the MRO for class B (and any other classes that inherit from class B).
    • Consistent across all subclasses: The algorithm should work uniformly when building MROs of classes that have common ancestors.
    • Avoid duplicate entries: Each class should appear only once in the MRO.
  2. super(): The super() function doesn’t necessarily refer to the immediate parent class but instead follows the MRO. This is crucial in cases of multiple inheritance.
  3. Class Hierarchies: Understanding how Python resolves method calls in complex hierarchies can prevent unexpected behavior and bugs in class designs involving multiple inheritance.