Python’s `NotImplemented` Type
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 NotImplementedclass 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.