from abc import ABC class AbstractIterable(ABC): @abstractmethod def __iter__(self): while False: yield None def get_iterator(self): return self.__iter__()
ABCs were added to strengthen the duck typing a little. If you inherited
AbstractIterable, then everybody knew you had an implemented
__iter__ method, and could handle that appropriately.
Unsurprisingly, this idea never caught on. People instead prefered “better ask forgiveness than permission” and wrapped calls to
__iter__ in a try block. This could be useful for static type checking, but in practice Mypy doesn’t use it. What if you wanted to typecheck it had
__iter__ but the person did not inherit from
AbstractIterable? The Mypy team instead uses protocols, which is bootstrapped off ABCs but hides that detail from the user.
But ABC was intended to be backwards compatible. And there were already existing classes that had a
iter method. How could we include them under our
AbstractIterable ABC? To handle this, the Python team added a special ABC method:
class AbstractIterable(ABC): @classmethod def __subclasshook__(cls, C): return hasattr(C, "__iter__")
__subclasshook__ is the runtime conditions that makes something count as a child of this ABC.
isinstance(OurClass(), AbstractIterable) is true if
OurClass has a
iter attribute, even if it didn’t inherit from
That function is a runtime function. We can put arbitrary code in it. It passes in the object’s class, not the object itself, so we can’t inspect the properties of the specific object. We can still do some weird stuff:
class PalindromicName(ABC): @classmethod def __subclasshook__(cls, C): name = C.__name__.lower() return name[::-1] == name
Any class with a palindromic name, like “Noon”, will counts as a child class of
PalindromicName. We can push this even further: why gaze into the abyss when you can jump in?
class NotIterable(ABC): @classmethod def __subclasshook__(cls, C): return not hasattr(C, "__iter__")
This is the type of everything that isn’t iterable. We have
isinstance(5, NotAString), etc. We’ve created a negatype: a type that only specifies what it isn’t. We can include this as part of a set of positive types, subtracting out a subset of a given type. There’s nothing stopping us from making an ABC for “iterables that aren’t strings”, or “callable objects that don’t return an object of the same callable”.
How is this useful?
ABCs aren’t checked as part of the method resolution order, so you can’t use this to patch in properties. Mypy can’t check
__subclasshook__. If you want it for runtime-checks, writing a function would be simpler and more portable than creating an ABC. Just about the only case where there is a difference is with single-dispatch functions, which can dispatch on virtual ABCs. But that’s about it.
It’s pretty cool, though!