I've noticed that duck typing works reasonably well up to a certain point in checking whether a class is an instance of an abstract class.
For example, to be an instance of collections.abc.Sized, we only need to define a __len__() method:
import collections.abc
class MySized:
def __len__(self):
return 0
assert isinstance(MySized(), collections.abc.Sized) # It is
In the same way, we can define classes that implement interfaces of Container, Iterable, Collection, and so on.
Failure befell me when I tried to write my own Sequence.
According to the documentation, as well as the code at cpython, a Sequence should be a Collection, a Reversible, and additionally implement methods __getitem__(), index() and count().
But duck typing is not enough:
import collections.abc
class MySequence:
def __getitem__(self, i):
return None
def __len__(self):
return 0
def __iter__(self):
return iter([])
def __reversed__(self):
return reversed([])
def __contains__(self, x):
return True
def index(self, value, start=0, stop=None):
return 0
def count(self, value):
return 0
assert isinstance(MySequence(), collections.abc.Collection) # Ok
assert isinstance(MySequence(), collections.abc.Reversible) # Ok
assert isinstance(MySequence(), collections.abc.Sequence) # Fail! It's not
Of course, if we explicitly inherit a class from an abstract base class, it works:
import collections.abc
class MySequence(collections.abc.Sequence):
def __getitem__(self, i):
return None
def __len__(self):
return 0
assert isinstance(MySequence(), collections.abc.Sequence) # It is
It would be interesting to understand the reasons for this behaviour and the limits of applicability of the interface implementation only. Perhaps I missed some other undocumented method? Or for some containers it's becoming necessary to explicitly inherit from an abstract ancestor?
From the docs (emphasis mine):
Without explicit inheritance you are relying on case 3, which is explicitly stated to not work for Sequences.