4

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Manu
  • 103
  • 6
  • 2
    You can check MRO like: `print(D.__mro__)` – JacobIRR Jul 09 '19 at 04:47
  • 1
    If you're new to Python, use Python3 https://pythonclock.org/ – OneCricketeer Jul 09 '19 at 04:53
  • 3
    Python 2 "new" style classes work the same in Python 3. But experimenting with Python 2 at this point in time is pretty much a waste of time. Python2 is all but reached end of life - just do your studies and experimentations (and, f course, projects), using Python 3. – jsbueno Jul 09 '19 at 04:55
  • 1
    Possible duplicate of [Is mro order depth-first or breadth-first?](https://stackoverflow.com/questions/47117327/is-mro-order-depth-first-or-breadth-first) – metatoaster Jul 09 '19 at 04:59
  • Is this "[MRO](https://en.wikipedia.org/wiki/C3_linearization)" thing still relevant in Python 3? – Peter Mortensen Jan 26 '23 at 03:04

3 Answers3

4

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jsbueno
  • 99,910
  • 10
  • 151
  • 209
3

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)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
KVaghs
  • 31
  • 3
0

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.

U13-Forward
  • 69,221
  • 14
  • 89
  • 114