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
__debug__. Similar to [
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 change 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.
__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.
__iand__(), etc.) for the same purpose. Also, its truth value is
>>> 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
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.py class 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
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
b1? Since in
__eq__() will check for
other being an instance of
B, we expect
a1.__eq__(b1) to deal with the comparison and return
>>> a1 == b1 Comparing an A with a B True
And that is the case. Now, if we compare
a1 (i.e. invoke
b1.__eq__(a1)), we would expect
NotImplemented to be returned. This is because
__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
b1.__eq__(a1) method returning
__eq__() method to be called and since a comparison between
B was defined in
__eq__() then we got the correct result (
And that is what returning
NotImplemented tells the runtime that it should ask someone else to satisfy the operation. In the expression
b1 == a1,
NotImplemented which tells Python to try
a1 knows enough to return
True, then the expression can succeed. If
__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
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.
Discussion on hackernews and reddit.