Search code examples
pythonpython-3.xmultiple-inheritance

Unexpected method call order in Python multiple inheritance


I have a child class named USA and it has two parent classes, A and B. Both parents have a method named change_to_4 and in B.__init__ I call the method, but instead of using the method that I defined in B it uses the A definition of the change_to_4 method.

class A:
    def __init__(self) -> None:
        self.a = 1
        super().__init__()

    def change_to_4(self):
        self.x = 4


class B:
    def __init__(self) -> None:
        self.b = 2
        self.change_to_4()
        super().__init__()

    def change_to_4(self):
        self.b = 4


class USA(A, B):
    def __init__(self) -> None:
        super().__init__()


print(f"A vars = {vars(A())}")
print(f"B vars = {vars(B())}")
print(f"USA vars = {vars(USA())}")

print(f"USA mro -> {USA.__mro__}")

I expect something like this:

A vars = {'a': 1}
B vars = {'b': 4}
USA vars = {'a': 1, 'b': 4}
USA mro -> (<class '__main__.USA'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

But the output is

A vars = {'a': 1}
B vars = {'b': 4}
USA vars = {'a': 1, 'b': 2, 'x': 4}
USA mro -> (<class '__main__.USA'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

Solution

  • When the interpreter looks up an attribute of an instance, in this case self.change_to_4 on an instance of USA, it first tries to find 'change_to_4' in self.__dict__, and failing to find it there, the interpreter then follows the method resolution order of USA (USA, A, B, object as shown in the output) to find the change_to_4 attribute in the first base class that has it defined.

    Excerpt from the documentation of Custom classes:

    Class attribute references are translated to lookups in this dictionary, e.g., C.x is translated to C.__dict__["x"] (although there are a number of hooks which allow for other means of locating attributes). When the attribute name is not found there, the attribute search continues in the base classes. This search of the base classes uses the C3 method resolution order which behaves correctly even in the presence of ‘diamond’ inheritance structures where there are multiple inheritance paths leading back to a common ancestor.

    In this case, A is the first base class of USA that defines change_to_4, so self.change_to_4() gets translated to A.change_to_4(self), resulting in self.x = 4 getting executed.

    That the call self.change_to_4() is made from a method in B does not change the fact that A comes before B in the method resolution order of a USA instance.