Minimum set of comparing operators to override
Categories:
Python Operator Overloading: The Minimum Set for Comparison

Understand which comparison operators you need to override in Python to ensure your custom objects behave as expected, covering __eq__, __lt__, and functools.total_ordering.
When creating custom classes in Python, you often want instances of these classes to be comparable. This means defining how Python should evaluate expressions like obj1 == obj2 or obj1 < obj2. Python achieves this through special methods, often called 'dunder' methods (due to their double underscores, e.g., __eq__). Overriding these methods allows you to dictate the comparison logic for your objects.
The Core Comparison Operators: __eq__ and __lt__
At a minimum, to enable comprehensive comparison for your custom objects, you typically need to implement __eq__ (equality) and __lt__ (less than). These two methods form the foundation upon which all other rich comparison operators can be derived. Python's functools.total_ordering decorator can then automatically fill in the rest.
flowchart TD
A["Custom Class"] --> B{"Implement `__eq__`"}
A --> C{"Implement `__lt__`"}
B & C --> D["Apply `@functools.total_ordering`"]
D --> E["All Rich Comparisons (==, !=, <, <=, >, >=) Available"]
subgraph Derived Operators
E --> F["__ne__ (from __eq__)"]
E --> G["__le__ (from __eq__ and __lt__)"]
E --> H["__gt__ (from __lt__)"]
E --> I["__ge__ (from __eq__ and __lt__)"]
endDerivation of Python comparison operators from __eq__ and __lt__ using total_ordering.
Let's break down why these two are crucial:
__eq__(self, other): This method defines what it means for two objects to be equal (==). If you don't implement it, Python's default behavior is to compare object identities (i.e.,id(self) == id(other)), which is rarely what you want for custom data types. It should returnTrueif the objects are equal,Falseotherwise, orNotImplementedif the comparison is not supported for the givenothertype.__lt__(self, other): This method defines what it means for one object to be less than another (<). This is the primary method for establishing an ordering. If you implement__lt__,functools.total_orderingcan then infer__le__,__gt__, and__ge__.
Leveraging functools.total_ordering
The functools.total_ordering decorator is a powerful tool that simplifies the implementation of rich comparison operators. If a class defines __eq__ and one of __lt__, __le__, __gt__, or __ge__, this decorator will automatically supply the remaining comparison methods. This significantly reduces boilerplate code and ensures consistency across all comparison operations.
from functools import total_ordering
@total_ordering
class MyObject:
def __init__(self, value):
self.value = value
def __eq__(self, other):
if not isinstance(other, MyObject):
return NotImplemented
return self.value == other.value
def __lt__(self, other):
if not isinstance(other, MyObject):
return NotImplemented
return self.value < other.value
# Example Usage:
obj1 = MyObject(10)
obj2 = MyObject(20)
obj3 = MyObject(10)
print(f"obj1 == obj3: {obj1 == obj3}") # True (from __eq__)
print(f"obj1 != obj2: {obj1 != obj2}") # True (from total_ordering)
print(f"obj1 < obj2: {obj1 < obj2}") # True (from __lt__)
print(f"obj2 > obj1: {obj2 > obj1}") # True (from total_ordering)
print(f"obj1 <= obj3: {obj1 <= obj3}") # True (from total_ordering)
print(f"obj2 >= obj1: {obj2 >= obj1}") # True (from total_ordering)
Implementing __eq__ and __lt__ with functools.total_ordering.
NotImplemented correctly in your comparison methods. If your object cannot be compared to other, returning NotImplemented allows Python to try the other object's comparison method, or fall back to default behavior if other also returns NotImplemented.Why Not Just __eq__ and __ne__?
While __eq__ and __ne__ (not equal) are often implemented together, they only cover equality comparisons. They do not provide any ordering information. If you need to sort objects, use them in sets/dictionaries (which rely on hashing and equality), or perform range checks, you'll need the ordering operators. __ne__ can actually be automatically derived from __eq__ (if __eq__ returns True, __ne__ returns False, and vice-versa), so explicitly implementing __ne__ is often redundant if __eq__ is present.
__eq__, you should also override __hash__ if your objects are mutable or if you intend to store them in hash-based collections like dict or set. If __eq__ is defined but __hash__ is not, Python will implicitly set __hash__ = None, making instances of your class unhashable.Summary of Comparison Operators
Here's a quick reference for the rich comparison operators and their relationships:

Python Rich Comparison Operators
By implementing __eq__ and __lt__ and applying @total_ordering, you provide a robust and complete set of comparison capabilities for your custom Python objects with minimal code.