3

I'm facing a nearly-textbook diamond inheritance problem. The (rather artificial!) example below captures all its essential features:

# CAVEAT: error-checking omitted for simplicity

class top(object):
    def __init__(self, matrix):
        self.matrix = matrix  # matrix must be non-empty and rectangular!

    def foo(self):
        '''Sum all matrix entries.'''
        return sum([sum(row) for row in self.matrix])

class middle_0(top):
    def foo(self):
        '''Sum all matrix entries along (wrap-around) diagonal.'''
        matrix = self.matrix
        n = len(matrix[0])
        return sum([row[i % n] for i, row in enumerate(matrix)])

class middle_1(top):
    def __init__(self, m, n):
        data = range(m * n)
        matrix = [[1 + data[i * n + j] for j in range(n)] for i in range(m)]

        super(middle_1, self).__init__(matrix)

In summary, classes middle_0 and middle_1 are both subclasses of class top, where middle_0 overrides method foo and middle_1 overrides method __init__. Basically, the classic diamond inheritance set up. The one elaboration on the basic pattern is that middle_1.__init__ actually invokes the parent class's __init__. (The demo below shows these classes in action.)

I want to define a class bottom that "gets"1 foo from middle_0 and __init__ from middle_1.

What's the "pythonic way" to implement such a bottom class?


Demo:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print top(matrix).foo()
# 45
print middle_0(matrix).foo()
# 15
print middle_1(3, 3).foo()
# 45

# print bottom(3, 3).foo()
# 15

1I write "gets" instead of "inherits" because I suspect this problem can't be solved easily using standard Python inheritance.

kjo
  • 33,683
  • 52
  • 148
  • 265
  • possible duplicate of [How does Python's super() work with multiple inheritance?](http://stackoverflow.com/questions/3277367/how-does-pythons-super-work-with-multiple-inheritance) – Igl3 Aug 18 '15 at 14:52
  • A blog that can possibly explain the multiple inheritance order to you: http://python-history.blogspot.de/2010/06/method-resolution-order.html – Igl3 Aug 18 '15 at 14:52

2 Answers2

4

bottom simply inherits from both; there is nothing specific about your classes that would make this case special:

class bottom(middle_0, middle_1):
    pass

Demo:

>>> class bottom(middle_0, middle_1):
...     pass
... 
>>> bottom(3, 3).foo()
15

This works as expected because Python arranges both middle_0 and middle_1 to be searched for methods before top is:

>>> bottom.__mro__
(<class '__main__.bottom'>, <class '__main__.middle_0'>, <class '__main__.middle_1'>, <class '__main__.top'>, <type 'object'>)

This shows the Method Resolution Order of the class; it is that order that is used to find methods. So bottom.__init__ is found on middle_1, and bottom.foo is found on middle_0, as both are listed before top.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Thanks for the detailed explanation. I must conclude that my example is somehow missing the feature of my actual problem that caused this solution to fail when I tried it (and prompted me to post the question). The code I'm working with is pretty large/complicated, so posting it would be impractical... I must investigate further. – kjo Aug 18 '15 at 14:59
  • @kjo the same principles apply; you need to look at the MRO to see where a method will be found. – Martijn Pieters Aug 18 '15 at 15:12
2

I think the

a class bottom that "gets"1 foo from middle_0 and __init__ from middle_1.

would be simply done by

class bottom(middle_0, middle_1):
    pass
socrates
  • 1,203
  • 11
  • 16