Search code examples
pythonmultiple-inheritancediamond-problem

Understanding multiple inheritence and super based on some code from David Beazly


I watched a screencast from David Beazly in which he implemeneted type checking using multiple or more specifically diamond inheritence. I thought that his approach looked really cool but it also confused me and I simply can't figure out how it is working. Here is the code im talking about:

class Contract:
    @classmethod
    def check(cls, value):
        pass


class Integer(Contract):
    @classmethod
    def check(cls, value):
        assert isinstance(value, int), 'Expected int'
        super().check(value)


class Positive(Contract):
    @classmethod
    def check(cls, value):
        assert value > 0, 'Must be > 0'
        super().check(value)


class PositiveInteger(Positive, Integer):
    pass

And here it is in action:

>>> PositiveInteger.check(-3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in check
AssertionError: Must be > 0
>>> PositiveInteger.check(4.88)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in check
  File "<stdin>", line 4, in check
AssertionError: Expected int

My Questions are:

  1. Why is the definition of a base class Contract with a method check needed to make this work?

  2. I have a basic understanding of what super does. I know it lets us avoid calling the base class explicitly and somehow deals with multiple inheritence. But what does it do in this example exactly?


Solution

  • Let's go through it line by line like a debugger.

    PositiveInteger.check(x)
    
    # Method resolution order:
    # PositiveInteger, Positive, Integer, Contract (from Positive), Contract (from Integer)
    
    # Look through MRO for .check() method. Found in Positive.
    
    assert x > 0
    super().check(value)
    
    # super() checks for next .check() method in MRO. Found in Integer
    
    assert isinstance(x, int)
    super().check(value)
    
    # super() checks for next .check() method in MRO. Found in Contract
    
    pass
    

    To easily find the method resolution order, use inspect.getmro().

    If you had explicitly used the base class, after Positive, the base class is Contract, so Integer would never be called.

    You need to define .check() in Contract as when you call the last super(), if Contract didn't have the .check() method, it would have raised an AttributeError, as super() wouldn't have been able to find it.