Python Method Resolution Order (MRO)

0

Let's say I have something like this:

# -*- coding: utf-8 -*-

class Base(object):

    @classmethod
    def foo(cls):
        pass

    def grok(self):
        pass


class A(Base):

    @classmethod
    def foo(cls):
        print('A')
        super().foo()

    def grok(self):
        print('a')


class B(Base):

    @classmethod
    def foo(cls):
        print('B')
        super().foo()

    def grok(self):
        print('b')


class C(A, B):

    pass

When I call the method grok doing C().grok() it returns me 'a' , which I think is correct since A methods takes precedence over B methods, but why when I call the methods of class C.foo() returns me 'A' and 'B' ? Should not I just return 'A' , since in solving methods in Python it looks for the methods in the class if it does not find search in its base class? Why even after finding the method in class A does it continue to search? It has something to do with the method being class, if so why?

    
asked by anonymous 04.06.2018 / 07:38

1 answer

2

No, it has no relation to the method being of class, but to calling super().foo() within the method.

You can observe the order of name resolution in Python 3.3+ using the mro function of the desired class. In this case, in class C would be:

print(C.mro())

The result is:

[
    <class '__main__.C'>, 
    <class '__main__.A'>, 
    <class '__main__.B'>, 
    <class '__main__.Base'>, 
    <class 'object'>
]

We can then draw the name resolution tree based on the above result:

   ,-------------.  
   |Classe object|  
   '-------------'  
         ^
         |
    ,-----------.  
    |Classe Base|  
    '-----------'  
         ^
         |
     ,--------.  
     |Classe B|  
     '--------'  
         ^
         |
     ,--------.  
     |Classe A|  
     '--------'  
         ^
         |
     ,--------.  
     |Classe C|  
     '--------'  

What can be summarized as:

  • When invoking the C.foo method, it will first search the class C ;
  • If it does not find, it will look for class A ;
  • If it still does not find it, it will look for class B ;
  • After, in class Base ;
  • And, finally, in object ;
  • If you still can not find, an exception will be thrown;
  • Therefore, when C.foo is invoked, the first definition of the method that is found is of class A , thus displaying the 'A' character on the screen; however, super().foo() is also invoked in this method. If you read the super() documentation, you will see that it returns an proxy for a class above in name resolution. In this way, when foo() , in class A , invokes super().foo() , the interpreter will look at the name resolution above, returning a proxy object for class B , thus displaying , the 'B' character results. In turn, the implementation of foo in B also invokes super().foo() and, according to the name resolution tree, super() will return a proxy object for class Base , which also implements the foo method, with no return, displaying None on the screen as a result.

    If the implementation in Base also invokes super().foo() , an exception would be triggered since super() would be a proxy object for class object and it does not implement method foo .

    This behavior is directly related to the Diamond Problem:

    Other interesting readings:

    04.06.2018 / 13:40