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:
NotImplementedis 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.NotImplementedErroris 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!