Skip to Content

Python Negatypes

Back in 2007 Python added Abstract Base Classes, which were intended to be used as interfaces:

from abc import ABC

class AbstractIterable(ABC):

    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):
    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 AbstractIterable.

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):
  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):
    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?

No idea.

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!