15.10. Method Resolution Order

Our tic-tac-toe program now has four classes to represent boards, three with defined getBoardStr() methods and one with an inherited getBoardStr() method, as shown in Figure 16-2.

_images/%E9%80%89%E5%8C%BA_013.png

Figure 16-2: The four classes in our tic-tac-toe board program

When we call getBoardStr() on a HybridBoard object, Python knows that the HybridBoard class doesn’t have a method with this name, so it checks its parent class. But the class has two parent classes, both of which have a getBoardStr() method. Which one gets called?

You can find out by checking the HybridBoard class’s MRO, which is the ordered list of classes that Python checks when inheriting methods or when a method calls the super() function. You can see the HybridBoard class’s MRO by calling its mro() method in the interactive shell:

from tictactoe_oop import * HybridBoard.mro()

[<class 'tictactoe_oop.HybridBoard'>, <class 'tictactoe_oop.HintBoard'>, <class 'tictactoe_oop.MiniBoard'>, <class 'tictactoe_oop.TTTBoard'>, <class 'object'>]

From this return value, you can see that when a method is called on HybridBoard , Python first checks for it in the HybridBoard class. If it’s not there, Python checks the HintBoard class, then the MiniBoard class, and finally the TTTBoard class. At the end of every MRO list is the built-in object class, which is the parent class of all classes in Python.

For single inheritance, determining the MRO is easy: just make a chain of parent classes. For multiple inheritance, it’s trickier. Python’s MRO fol- lows the C3 algorithm, whose details are beyond the scope of this book. But you can determine the MRO by remembering two rules:

  • Python checks child classes before parent classes.

  • Python checks inherited classes listed left to right in the class statement.

If we call getBoardStr() on a HybridBoard object, Python checks the HybridBoard class first. Then, because the class’s parents from left to right are HintBoard and MiniBoard , Python checks HintBoard . This parent class has a getBoardStr() method, so HybridBoard inherits and calls it.

But it doesn’t end there: next, this method calls super().getBoardStr() . Super is a somewhat misleading name for Python’s super() function, because it doesn’t return the parent class but rather the next class in the MRO. This means that when we call getBoardStr() on a HybridBoard object, the next class in its MRO, after HintBoard , is MiniBoard , not the parent class TTTBoard . So the call to super().getBoardStr() calls the MiniBoard class’s getBoardStr() method, which returns the miniature tic-tac-toe board string. The remaining code in the HintBoard class’s getBoardStr() after this super() call appends the hint text to this string.

If we change the HybridBoard class’s class statement so it lists MiniBoard first and HintBoard second, its MRO will put MiniBoard before HintBoard . This means HybridBoard inherits getBoardStr() from MiniBoard , which doesn’t have a call to super() . This ordering is what caused the bug that made the miniature tic- tac-toe board display without hints: without a super() call, the MiniBoard class’s getBoardStr() method never calls the HintBoard class’s getBoardStr() method.

Multiple inheritance allows you to create a lot of functionality in a small amount of code but easily leads to overengineered, hard-to-understand code. Favor single inheritance, mixin classes, or no inheritance. These tech- niques are often more than capable of carrying out your program’s tasks.