Method resolution order in Python

Question:

I am new to Python. I am using Python 2.7. I was going through method order resolution with a small snippet as follows:

class A(object):
    attr = 'A'

class B(A):
    pass

class C(A):
    attr = 'C'

class D(B,C):
    pass

x = D()
print x.attr

The order of resolution is x, D, B, C, A and hence the output would be C. Going by the above example, I made a small change to the code.

class A(object):
    attr = 'A'

class E(object):
    attr = 'E'

class B(A):
    pass

class C(E):
    attr = 'C'

class D(B,C):
    pass

x = D()
print x.attr

Going by my previous example, I expected the order to be x, D, B, C, A, E. To my surprise the output was "A". Hence I got confused with the order of resolution in new-style class. When was B’s parent accessed before C class?

Asked By: Manu

||

Answers:

It’s because of the order, so if D is:

class D(C,B):
    pass

It will output C because it gets the attr attribute of the first class inherited.

Answered By: U13-Forward

If you stop to think about it, it is just the intuitive way of working. This article, which by now just looks like an archaeological find, is still the authoritative description and reasoning on Python’s method resolution order algorithm.

But despite the technical details in there, what happen in your two examples are:

In the first one, D,B,C,A, the path through B indicates that A‘s attribute should be used. But As attribute itself is shadowed by the attribute in C – that is, the declaration in C overrides the attr declared in A. Therefore it is the one used.

In the second hierarchy, D,B,C,A,E, B coming first than C, again indicates that A.attr should be used. This time, however, A’s own attribute had not been shadowed by another class in the hierarchy – rather C.attr comes from another "lineage" – so the language picks the first it encounters.

That is the "plain English description" of what is going on. The authoritative article linked above lays the formal rules for that :

the linearization of [a class] C is the sum of C plus the merge of the
linearizations of the parents and the list of the parents.

[given class C(B1, …, BN):], take the head of the first list, i.e L[B1][0] [linearization (aka mro) of Base B1 up to Object – the head is B1 -]; if this head is not in
the tail of any of the other lists [linearization lists for the other bases] , then add it to the linearization
of C and remove it from the lists in the merge, otherwise look at the
head of the next list and take it, if it is a good head. Then repeat
the operation until all the class are removed or it is impossible to
find good heads. In this case, it is impossible to construct the
merge, Python 2.3 [and subsequent versions] will refuse to create the class C and will raise an
exception.

Bringing in your second example, you have
D(B, C) – the linearizations for B and C are: [B, A, object] and [C, E, object] and the linearization of D starts by taking "B", checking it is not
on the tail of any other lists (and it is not on [C, E, object]), then B is taken. The remaining lists are [A, object] and [C, E, object] – the algorithm then picks A it is not in the other list, then A is appended to the mro of D. It then picks object. It is on the other list. So the algorithm leaves the first list intact, and takes C, E and finally object, for D, B, A, C, E, object linearization.

In your first example, the linearization of both bases are [B, A, object] and [C, A, object] when the algorithm checks for A, it is be on the tail of the second list – so, C is picked first than A from the second list – the final linearization is D, B, C, A, object.

Answered By: jsbueno

We will understand the method resolution order (MRO) by taking the examples of classical Python classes only. Remember, that, this concept of MRO is only applicable up to Python 2.7 as Python 3 does not support classical Python classes.

Consider the following example:

class A():
    #pass
    def who_am_i(self):
        print("I am a A")


class B(A):
    #pass
    def who_am_i(self):
        print("I am a B")


class C(A):
    #pass
    def who_am_i(self):
        print("I am a C")

class D(B,C):
    #pass
    def who_am_i(self):
       print("I am a D")

d1 = D()
d1.who_am_i()

Here’s the output of the above program:

I am a D

From the output, we can see that the method of class D is being called first as expected. This is because class D is at the lowest hierarchy of the classes where it inherits class B and class C. Now the question regarding method of which class would be resolved first arises when the called method in the instance of class D is not available in class D itself and the interpreter has to climb one level higher where both the parent classes of D,i.e. class B and class C are available. So, let us remove this method from class D and see what does interpreter do.

class A():
    #pass
    def who_am_i(self):
        print("I am a A")


class B(A):
    #pass
    def who_am_i(self):
        print("I am a B")


class C(A):
    #pass
    def who_am_i(self):
        print("I am a C")

class D(B,C):
    pass

d1 = D()
d1.who_am_i()

Here’s the output of the above program:

I am a B

Even though, B and C both had the required method, interpreter called the method of class B and not the method from class C.

You can find more examples here. Method Resolution Order in Python (MRO)

Answered By: KVaghs