Custom types that sort, compare or go into heaps need all six comparison methods to agree.
For objects to work with sorted, min, max, bisect and heapq, they need comparison methods. Python has six: __eq__, __ne__, __lt__, __le__, __gt__, __ge__. __ne__ is derived from __eq__ automatically, but the four ordering methods are not.
@total_ordering is a class decorator: define __eq__ and one of __lt__, __le__, __gt__ or __ge__, and it supplies the other three.
@total_ordering
class Version:
def __eq__(self, other): ...
def __lt__(self, other): ...Implementation tips:
- Return
NotImplemented(notFalse) whenotheris an unsupported type. Python then tries the reflected operation and finally raises a cleanTypeError. - Compare tuples of fields:
(self.major, self.minor) < (other.major, other.minor)handles lexicographic order for you. - If you define
__eq__, define__hash__too if instances should be hashable (otherwise they become unhashable).
The generated methods add a small overhead per comparison; for hot sorting paths, define all methods yourself or use a key= function. For simple records, @dataclass(order=True) generates all comparisons from the fields in order, which is often easier still.
Example
from functools import total_ordering
@total_ordering
class Version:
def __init__(self, text):
self.parts = tuple(int(p) for p in text.split("."))
def __eq__(self, other):
if not isinstance(other, Version):
return NotImplemented
return self.parts == other.parts
def __lt__(self, other):
if not isinstance(other, Version):
return NotImplemented
return self.parts < other.parts
def __hash__(self):
return hash(self.parts)
def __repr__(self):
return "v" + ".".join(map(str, self.parts))
vs = [Version("1.10.0"), Version("1.2.3"), Version("1.2")]
print(sorted(vs)) # [v1.2, v1.2.3, v1.10.0]
print(Version("2.0") >= Version("1.9")) # True (generated)
print(max(vs), Version("1.2") in {Version("1.2")}) # v1.10.0 TrueRemember
- Define
__eq__plus one ordering method; @total_ordering adds the rest - Return
NotImplementedfor unsupported types - Consider
@dataclass(order=True)or a key function instead
Watch out for
- Returning False instead of NotImplemented for foreign types, which hides type errors.