RC: W5 D5 — When to use `NotImplemented` and `NotImplementedError`
March 15, 2024Today I spent most of my day preparing a presentation on databases that I will give on Monday, so not much coding done. So I thought I would share a small thing I learned this week to distinguish “not implemented” cases.
In the Python source code chunk I shared yesterday, after executing the comparison operation, there is a check testing
whether the result is Py_NotImplemented
. This corresponds to cases where we return NotImplemented
in Python.
There is also a raise NotImplementedError
that is sometimes used, and I was a bit confused as to when to use one or
the other.
Here is what I figured:
NotImplemented
is a special value meant to indicate that the operation is not implemented with respect to the other type (as mentioned in the docs). So it could be used when implementing comparison methods for example.NotImplementedError
is an exception meant to be raised when a class is being developed and the method called has not been implemented yet, or to indicate that an abstract method from a base class is meant to be overridden by derived classes (as mentioned in the docs).
Here is a small snippet of code that should make the difference between the two clearer:
class MyParentClass:
def my_method(self):
raise NotImplementedError("Subclasses must implement this method.")
class MyDerivedClass(MyParentClass):
def my_method(self):
print("MyDerivedClass implementation of my_method.")
class MyComparisonClass:
def __eq__(self, other):
if not isinstance(other, MyComparisonClass):
return NotImplemented
# comparison logic here
What is interesting about explicitly declaring the return NotImplemented
is that it allows to prevent unintended
behaviors.
For example, let’s suppose we have a class whose __eq__
method operates on the key
attribute but does not explcitly
state a NotImplemented
case:
class MyClass1:
def __init__(self, key):
self.key = key
def __eq__(self, other):
return self.key == other.key
class MyClass2:
def __init__(self, key, value):
self.key = key
self.value = value
def __eq__(self, other):
return self.key == other.key and self.value == other.value
With this, executing MyClass1(key="a") == MyClass2(key="a", value="a")
would indicate that the two objects are equal
(because they have the same key
) without giving any warning.
By adding the NotImplemented
case when the second object is of a different instance, we are warned that the comparison
being executed might not be what is intended – which is much better!