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):
An
issubclass()
orisinstance()
test for an interface works in one of three ways.
A newly written class can inherit directly from one of the abstract base classes
Existing classes and built-in classes can be registered as “virtual subclasses” of the ABCs.
Some simple interfaces are directly recognizable by the presence of the required methods (unless those methods have been set to
None
). Complex interfaces do not support this last technique because an interface is more than just the presence of method names. For example, knowing that a class supplies__getitem__
,__len__
, and__iter__
is insufficient for distinguishing aSequence
from aMapping
.
Without explicit inheritance you are relying on case 3, which is explicitly stated to not work for Sequences.