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__)"] end
Derivation 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 returnTrue
if the objects are equal,False
otherwise, orNotImplemented
if the comparison is not supported for the givenother
type.__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_ordering
can 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.