How functools.total_ordering Works in Python
The functools.total_ordering class decorator simplifies
implementing ordered types by automatically synthesizing missing
comparison dunder methods from a minimal set of user-defined operations.
To use it, a class must define __eq__ and at least one
ordering method: __lt__, __le__,
__gt__, or __ge__. The decorator inspects the
class at definition time, selects the single defined ordering method as
a "root," and generates the remaining three comparison methods by
applying logical negations and combinations of that root method and
__eq__.
The Requirements
A complete set of rich comparison methods in Python consists of six dunder methods:
__eq__(equal to)__ne__(not equal to)__lt__(less than)__le__(less than or equal to)__gt__(greater than)__ge__(greater than or equal to)
Python automatically provides a fallback implementation for
__ne__ based on the inverse of __eq__.
However, Python does not automatically infer relative ordering between
< and > without explicit code.
functools.total_ordering bridges this gap by requiring
developers to supply only two methods: __eq__ and one of
__lt__, __le__, __gt__, or
__ge__. If neither condition is met, the decorator raises a
ValueError.
Internal Mapping Logic
The mathematical principle behind the decorator is that any total ordering can be fully derived from equivalence plus a single strict or non-strict inequality. Under the hood, the Python standard library maintains a conversion table mapping the chosen root method to the missing target operations.
Derivation from __lt__
When __lt__ is provided alongside
__eq__:
__le__(self, other): Evaluated asself < other or self == other.__gt__(self, other): Evaluated asnot (self < other or self == other).__ge__(self, other): Evaluated asnot (self < other).
Derivation from __le__
When __le__ is provided:
__lt__(self, other): Evaluated asself <= other and not (self == other).__gt__(self, other): Evaluated asnot (self <= other).__ge__(self, other): Evaluated asnot (self <= other) or self == other.
Derivation from __gt__
When __gt__ is provided:
__ge__(self, other): Evaluated asself > other or self == other.__lt__(self, other): Evaluated asnot (self > other or self == other).__le__(self, other): Evaluated asnot (self > other).
Derivation from __ge__
When __ge__ is provided:
__gt__(self, other): Evaluated asself >= other and not (self == other).__le__(self, other): Evaluated asnot (self >= other) or self == other.__lt__(self, other): Evaluated asnot (self >= other).
Execution Mechanics
When Python executes a class definition decorated with
@total_ordering, the following sequence occurs:
- Introspection: The decorator checks the class
namespace using
dir()or__dict__to see which comparison methods are explicitly defined. - Root Selection: It selects the first available
ordering method from a prioritized list (
__lt__,__le__,__gt__,__ge__) to act as the primary comparator. - Closure Construction: For each missing method, the
decorator creates a closure that calls the root method and
__eq__. These generated functions properly handleNotImplementedreturns, ensuring that if an unsupported operand is compared, Python can gracefully fall back to reflected operators (e.g., tryingother.__gt__(self)ifself.__lt__(other)returnsNotImplemented). - Class Mutation: The newly constructed functions are
assigned directly to the class using
setattr(cls, op_name, op_func). - Metadata Update: The decorator updates the
docstrings and names of the generated methods using
functools.update_wrapperso they resemble standard class methods.
Performance and Behavior Considerations
Because the generated methods rely on executing combinations of the
underlying root method and __eq__, comparing objects using
synthesized operators introduces minor overhead from multiple Python
function calls. If an application performs heavy, performance-critical
sorting or comparisons, manually implementing all four dunder methods
directly can avoid the redundant function evaluation inherent in the
decorator's synthesized logic.