Python Method resolution order (MRO)

Asked

Viewed 140 times

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 making C().grok() the same returns to me 'a', which I think is correct since the methods of A takes precedence over those of B, but why when I call the class methods C.foo() get back to me 'A' and 'B'? You shouldn’t just return 'A', since in method solving 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 the class A he continues to search? It has something to do with the method being class, if yes why?

  • I tested your code here and only returned A.

  • What did you intend to do with super().foo() in the method itself foo?

1 answer

2


No, it has nothing to do with the method being class, but with the call super().foo() within the method.

You can observe the order of name resolution in Python 3.3+, using the function mro of the desired class. In this case, in the 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|  
     `--------'  

Which can be summed up as:

  1. When invoking the method C.foo, first pick up in class C;
  2. If you can’t find it, you’ll pick it up in class A;
  3. If not already found, will be searched in class B;
  4. After, in class Base;
  5. And finally, in object;
  6. If still not found, an exception will be fired;

So when it’s invoked C.foo, the first definition of the method that is found is of the class A, thus displaying the character 'A' on the screen; however, this method is also invoked super().foo(). If you read the function documentation super(), you will see that it returns an object proxy for a class above in name resolution. Thus, when foo(), in class A, invokes super().foo(), the interpreter will look at the above name resolution, returning an object proxy for the class B, thus displaying the character 'B' as a result. In turn, the implementation of foo in B also invokes super().foo() and, according to the name resolution tree, super() return an object proxy for the class Base, which also implements the method foo, with no return, exhibiting None on screen as a result.

If the implementation in Base also invoke super().foo(), an exception would be fired, whereas super() would be an object proxy for the class object and this does not implement the foo.

This behavior is directly related to the Diamond Problem:

Other interesting readings:

Browser other questions tagged

You are not signed in. Login or sign up in order to post.