Python’s `NotImplemented` Type

Shahriar Tajbakhsh
3 min readMay 18, 2018

--

This post discusses Python’s NotImplemented built-in constant/type; what it is, what it means and when it should be used.

What is it?

>>> type(NotImplemented)
<type 'NotImplementedType'>

NotImplemented is one of Python’s six constants living in the built-in namespace. The others are False, True, None, Ellipsis and __debug__. Similar to Ellipsis, NotImplemented can be reassigned (shadowed). Assignments to it, even as an attribute name, do not raise a SyntaxError. So it isn’t really a “real/true” constant. Of course, we should never ever re-assign it. But, for completeness:

>>> None = 'hello'
...
SyntaxError: can't assign to keyword
>>> NotImplemented
NotImplemented
>>> NotImplemented = 'do not'
>>> NotImplemented
'do not'

What does it mean and when should it be used?

NotImplemented is a special value which should be returned by the binary special methods (e.g. __eq__(), __lt__(), __add__(), __rsub__(), etc.) to indicate that the operation is not implemented with respect to the other type; it may be returned by the in-place binary special methods (e.g. __imul__(), __iand__()) for the same purpose. Also, its truth value is True:

>>> bool(NotImplemented)
True

You might be asking yourself, “But I thought I should raise a NotImpementedError when an operation is not implemented!”. We’ll see, with some examples, why that shouldn’t be the case when implementing binary special methods.

Let’s show the use of the NotImplemented constant by coding __eq__() for two very basic (and useless) classes A and B. [For this simple example, __ne__() won’t be implemented to avoid distraction, but in general, every time __eq__() is implemented, __ne__() should also be implemented unless there is a good reason for it not to be.]

# example.pyclass A(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
if isinstance(other, A):
print('Comparing an A with an A')
return other.value == self.value
if isinstance(other, B):
print('Comparing an A with a B')
return other.value == self.value
print('Could not compare A with the other class')
return NotImplemented
class B(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
if isinstance(other, B):
print('Comparing a B with another B')
return other.value == self.value
print('Could not compare B with the other class')
return NotImplemented

Now, in the interpreter:

>>> from example import A, B
>>> a1 = A(1)
>>> b1 = B(1)

We can now experiment with different calls to __eq__() and see what happens. As a reminder, in Python, a == b results in a.__eq__(b) being called:

>>> a1 == a1
Comparing an A with an A
True

As expected, a1 is equal to a1 (itself) and the __eq__() in class A took care of this comparison. Comparing b1 with itself will also yield a similar result:

>>> b1 == b1
Comparing a B with another B
True

What if we now compare a1 with b1? Since in A’s __eq__() will check for other being an instance of B, we expect a1.__eq__(b1) to deal with the comparison and return True:

>>> a1 == b1
Comparing an A with a B
True

And that is the case. Now, if we compare b1 with a1 (i.e. invoke b1.__eq__(a1)), we would expect NotImplemented to be returned. This is because B’s __eq__() only compares against other B instances. Let’s see what happens:

>>> b1 == a1
Could not compare B against the other class
Comparing an A with a B
True

Clever! b1.__eq__(a1) method returning NotImplemented caused A’s __eq__() method to be called and since a comparison between A and B was defined in A’s __eq__() then we got the correct result (True).

And that is what returning NotImplemented does. NotImplemented tells the runtime that it should ask someone else to satisfy the operation. In the expression b1 == a1, b1.__eq__(a1) returns NotImplemented which tells Python to try a1.__eq__(b1). Since a1 knows enough to return True, then the expression can succeed. If A’s __eq__() also returned NotImplemented, then the runtime would fall back to the built-in behaviour for equality which is based on object identity (which in CPython, is the object’s address in memory).

Note that raising a NotImpementedError when b1.__eq__(a1) fails would break out of the code unless caught, whereas NotImplemented doesn’t get raised and can result in/be used for further tests.

--

--

Shahriar Tajbakhsh

Co-founder & CTO at Metaview. Previously, Palantir, Osper, and Morgan Stanley. I love a silly salmon with a good egg. @s16h_ on Twitter.