pythonmethod-resolution-order

How Method resolution Order works in python


I have a code here:

class Prey:
    def __init__(self):
        super().__init__()
        print("Prey's init")

class Predator:
    def __init__(self):
        super().__init__()
        print("Predator's init")

class Rabbit(Prey):
    def __init__(self):
        super().__init__()
        print("Rabbit's init")

class Fish(Prey, Predator):
    def __init__(self):
        super().__init__()
        print("Fish's init")

    
class Hawk(Predator):
    def __init__(self):
        super().__init__()
        print("Hawk's init")

class Creature(Rabbit, Fish, Hawk):
    def __init__(self):
        super().__init__()
        print("Creature's init")

c = Creature()

The Relationship of the above code is here:

    Prey  Predator
    /  \  /     \
Rabbit    Fish    Hawk
     \     |     /
        Creature

And the output of the above code is here:

Predator's init
Hawk's init
Prey's init
Fish's init
Rabbit's init
Creature's init

I need to understand how Method resolution Order works in python and why this specific output? (If someone have some better tutorial you can refer, please feel free to paste the link.) I have some other code which I can't paste it here, so I'm pasting here with a silly example.


Solution

  • The MRO is basically a linearization of a directed acyclic graph. In this case, the nodes of the graph are the classes, and a directed edge connects a class to its parent. There are no cycles, because no class can indirectly inherit from one of its own descendants. A linearization is just a fixed ordering of the classes.

    An obvious constraint on this ordering is that if A inherits from B, then A should appear before B. What's less obvious is how classes not related to each other should be ordered. In order to obey a property known as monotonicity, we require (among other things) that the parents of a class be listed in the same order in the linearization as in the class definition. That is, given

    class A(B, C):
        ...
    

    not only does A appear before B and C, but B must appear before C as well in a monotonic linearization.

    These two facts alone are sufficient to limit our choices of a monotonic linearization in your example to

    1. Creature, Rabbit, Fish, Hawk, Prey, Predator, object
    2. Creature, Rabbit, Fish, Prey, Hawk, Predator, object

    So why does Python (or rather, the C3 linearization algorithm) prefer #2 to #1? It imposes an additional constraint, based on "similarity". If A is "more like" B than like C, then B precedes C. In this case, a Creature is more like a Prey than a Hawk, because it is more like a Fish than a Hawk (because Fish precedes Hawk in the parent list), and a Fish is kind of Prey.