Search code examples
pythonmultiple-inheritancemethod-resolution-order

How to customize the method resolution order (mro) of Python?


I would like to customize the my inherited methods are called.

Here's an example code:

class First(object):
    def get(self):
        print('getting from first')

    def set(self):
        print('setting to first')

class Second(object):
    def get(self):
        print('getting from second')

    def set(self):
        print('setting to second')

class Third(First, Second):
    def get(self):
        super(Third, self).get()

    def set(self):
        super(Third, self).set()

Now the behavior i would like to have is such that:

third = Third()
third.get() # -> should print 'getting from first'
third.set() # -> should print 'setting to second'

Right now the mro shows:

Third.__mro__ ->  (__main__.Third, __main__.First, __main__.Second, object)

We can see that methods inside main.First always get called first. While what i want is that main.Second gets called first during execution of set() method.

And here's my attempt to solve this, trying to modify the MRO of Third class:

The idea is to swap the two positions of the two classes and see if it can work. First, a swap() helper function.

def swap(index1, index2, mro_tuple):
    l = list(mro_tuple)
    temp = l[index1]
    l[index1] = l[index2]
    l[index2] = temp
    return tuple(l)

Then during implemenation of set() method, i attempt to modify the mro of the underlying class.

class Third(First, Second):
    def get(self):
        super(Third, self).get()
    def set(self):
        self.__class__.__mro__ = swap(1, 2, self.__class__.__mro__) # swap here..
        super(Third, self).set() # then call method**
In [43]: third = Third() 

In [44]: third.get()                                                            
getting from first

In [45]: third.set()                                                            
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-53-c82ac1a0d5bc> in <module>
----> 1 third.set()

<ipython-input-50-00c9baff0d57> in set(self)
      4 
      5     def set(self):
----> 6         self.__class__.__mro__ = swap(1, 2, self.__class__.__mro__) # swap here..
      7         super(Third, self).set() # then call method
      8 

AttributeError: readonly attribute

It shows that the __mro__ attribute cannot be reset.

Is there anyway to implement this behavior, in a convenient way ?


Solution

  • Your best bet is probably to have Third explicitly use the Second implementation:

    class Third(First, Second):
        set = Second.set
    

    although the fact that you're asking this at all is a warning sign that you may have picked a bad class hierarchy.