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:
Why is the definition of a base class Contract with a method check needed to make this work?
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?
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.