Method resolution order in python

纵饮孤独 提交于 2021-02-16 14:33:08

问题


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. Can someone clarify on when B's parent was accessed before C class. Thanks.


回答1:


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 tecnical 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 comming 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 subsequente 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 lineariation is D, B, C, A, object.




回答2:


We will understand the method resolution order 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.

More Examples you can find here. Method Resolution Order in Python (MRO)




回答3:


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.



来源:https://stackoverflow.com/questions/56945459/method-resolution-order-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!