Alex Lin Wang 王帅

NotImplemented

Use the NotImplemented constant for binary operations and method delegation to enable proper operator fallback behavior.

NotImplemented is a built-in constant used for binary special methods to show that a specific type does not have an implementation. The purpose is to tell Python that "I'm not sure what to do with this other type—try the other operand's reflected method or fall back".

Important: NotImplementedError is an exception you raise to say that a method exists but has yet to be implemented. Don't confuse the two!

Do not raise NotImplemented as an error:

class Storage:
    def get(self, key):
        raise NotImplemented    # This is wrong!

Do not call NotImplemented—it's not callable:

>> NotImplemented()
TypeError: "NotImplementedType" object is not callable

Binary operators take two operands, with Python defining three different hooks:

  • Left: __add__, __sub__, __mul__, __truediv__
  • Right/reflected: __radd__, __rsub__, __rmul__, __rtruediv__
  • In-place/augmented assignment: __iadd__, __isub__, __imul__, __itruediv__

Python understands that NotImplemented is a special sentinel and follows this strategy for a + b:

  1. Subclass check: If type(b) is a strict subclass of type(a) and defines __radd__, call b.__radd__(a) first
  2. Left side: Call a.__add__(b) first (if not handled above)
  3. Check result: If it returns anything except NotImplemented, we stop
  4. Right side fallback: If it returns NotImplemented, try b.__radd__(a)
  5. TypeError: If both sides return NotImplemented, raise TypeError

Here's an example of NotImplemented being used for binary operators:

class Left:
    def __add__(self, other):  # left side can't handle it
        return NotImplemented

class Right:
    def __radd__(self, other):  # right side handles the reversed op
        return "Right handled it"

print(Left() + Right())  # works because NotImplemented allowed the call for __radd__

For a more complex example showing the full fallback chain:

class L:
    def __iadd__(self, other): 
        print("iadd")
        return NotImplemented
    def __add__(self, other):  
        print("add")
        return NotImplemented

class R:
    def __radd__(self, other): 
        print("radd")
        return "handled"

x = L()
x += R()   # prints: iadd -> add -> radd

NotImplemented also works with rich comparisons. These are "rich" compared to the old single __cmp__ from Python 2:

class L:
    def __lt__(self, other):  # can't compare
        return NotImplemented

class R:
    def __gt__(self, other):  # handles reversed "<"
        return True

print(L() < R())  # True (via R.__gt__)

When both sides can handle the operation, the first successful one wins:

class A:
    def __eq__(self, other):
        return NotImplemented

class B:
    def __eq__(self, other):
        return True

print(A() == B())  # True (via B.__eq__)

Use NotImplemented when your class doesn't know how to handle a specific operation with another type. This allows Python to try alternative approaches rather than immediately failing, enabling more flexible and extensible operator behavior.

Questions or feedback? Feel free to reach out!