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?