What does @functools.total_ordering derive, and what must the class already define?
answer
- Stops you writing six near-identical methods
- A class decorator, not a function decorator
- Needs equality plus one ordering method
- The other three are generated from yours
- Raises ValueError if no ordering method exists
basics
~10 sfunctools.total_ordering is a class decorator that fills in the ordering operators you did not write. Define eq plus one of lt, le, gt or ge, and it derives the other three.
solid answer
~40 s`functools.total_ordering` is a class decorator that saves you from hand-writing every comparison operator. You supply `__eq__` and exactly one ordering method — `__lt__`, `__le__`, `__gt__` or `__ge__` — and the decorator adds the three missing ones, each defined in terms of the operator you wrote plus `__eq__`. `__ne__` needs nothing: Python 3 derives it from `__eq__` automatically. If the class defines none of the four ordering methods, the decorator raises `ValueError` at class-creation time. It only fills in operators the class itself does not define, so a hand-written method always wins. The cost is speed and clarity: a derived `__ge__` is a Python-level call that invokes your `__lt__` and possibly `__eq__`, so a hot comparison path is better served by writing all six by hand.
code
python · 25 linesfrom functools import total_ordering
@total_ordering
class Release:
def __init__(self, major, minor):
self.major = major
self.minor = minor
def _key(self):
return (self.major, self.minor)
def __eq__(self, other):
if not isinstance(other, Release):
return NotImplemented
return self._key() == other._key()
def __lt__(self, other):
if not isinstance(other, Release):
return NotImplemented
return self._key() < other._key()
a, b = Release(3, 14), Release(3, 9)
print(a > b, a <= b, a >= b, sorted([a, b])[0].minor)go deeper
Be ready to recall the two ingredients: eq plus any one of the four ordering methods, and the decorator supplies the rest. Knowing it sits in functools and is applied above the class statement is most of the answer.
Explain the mechanics: which slots are filled, that a hand-written operator is left alone, that ne comes free from eq, and that the decorator raises ValueError at import time when no ordering method exists.
Show judgement about the cost. Derived comparisons are extra Python-level calls in the hottest part of any sort, so be able to say when you would hand-write all six and how you would measure that decision rather than guess it.
Own the consistency argument. A codebase where every value type derives its ordering the same way is easier to reason about than scattered hand-rolled operators, and you should be able to say where you draw the line between that uniformity and the types whose ordering is partial and must never be generated.
## The problem it solves Python does not have a single "compare" hook. Ordering comes from six rich-comparison special methods — `__eq__`, `__ne__`, `__lt__`, `__le__`, `__gt__`, `__ge__` — and the interpreter calls exactly the one matching the operator in the source. Write only `__lt__` on a value class and `x <= y` will not work: it falls through to the default from `object`, which returns `NotImplemented`, and the interpreter raises `TypeError`. Hand-writing all of them means five near-identical methods that must stay consistent with one another forever. `functools.total_ordering` is the standard-library answer. It is a **class** decorator (it takes and returns the class, not a function), and it is applied like this: ```python from functools import total_ordering @total_ordering class Release: def __init__(self, major, minor): self.major, self.minor = major, minor def _key(self): return (self.major, self.minor) def __eq__(self, other): if not isinstance(other, Release): return NotImplemented return self._key() == other._key() def __lt__(self, other): if not isinstance(other, Release): return NotImplemented return self._key() < other._key() ``` ## What you must provide Two things: 1. **`__eq__`** — the derived operators need equality to distinguish "less" from "less or equal". Inheriting `object`'s identity-based `__eq__` technically satisfies the decorator, but the derived results will then be about identity, which is almost never what a value class wants. 2. **One of `__lt__`, `__le__`, `__gt__`, `__ge__`** — any one of the four is enough; the decorator works out the rest from whichever it finds. If it finds none, it raises `ValueError` with the message *"must define at least one ordering operation: < > <= >="* — and it raises at decoration time, when the module is imported, not on first comparison. Note what is **not** on the list. `__ne__` is not needed: since Python 3.0 the default `__ne__` inverts whatever `__eq__` returns, so defining `__eq__` alone gives you `!=` for free. And the decorator does nothing about hashing — that is governed by the ordinary rule that a class defining `__eq__` gets `__hash__` set to `None` unless it defines one. ## What it generates For each of the three operators the class does not define, the decorator installs a small function written in terms of the one you did. Derived from `__lt__`, they are effectively: ```python def __gt__(self, other): # not less, and not equal result = type(self).__lt__(self, other) return result if result is NotImplemented else (not result and self != other) def __le__(self, other): # less, or equal result = type(self).__lt__(self, other) return result if result is NotImplemented else (result or self == other) def __ge__(self, other): # simply not less result = type(self).__lt__(self, other) return result if result is NotImplemented else not result ``` Two details matter. First, each derived method **propagates `NotImplemented`** rather than converting it to a boolean, so cross-type comparisons still fall back to the other operand and then to a clean `TypeError`. Second, the derived methods are only installed for operators the class itself does not define: the decorator compares each slot against the default inherited from `object`, and any method the class wrote by hand is left untouched. So you can decorate a class, hand-write `__ge__` for speed, and keep the two you don't care about generated. ## Costs and limits The documentation is explicit that this convenience is slower than hand-writing the operators: every derived comparison is an extra Python-level function call, and some of them call `__eq__` as well, so a `__gt__` on a decorated class can cost two dispatches where a hand-written one costs zero extra. In a sort of a large list — where comparisons dominate — that is measurable. The usual advice is to reach for the decorator by default and hand-write all six only if profiling says the comparisons are hot. The deeper limit is semantic: the derived definitions assume a **total** order, where any two values are related by exactly one of `<`, `==`, `>`. For a partially ordered type — set-like containers, where neither `a < b` nor `a == b` nor `a > b` need hold — "not less than" is simply not the same as "greater than or equal", and the generated operators will produce confidently wrong answers. Such types must define all six by hand. Finally, when a class is a plain record whose ordering is field-by-field, there is a cheaper route still: a dataclass declared with `order=True` generates all four ordering methods that compare the fields as a tuple, with no decorator and no hand-written `__lt__` at all. Reach for `total_ordering` when the ordering logic is genuinely yours to write.
- What happens if you apply functools.total_ordering to a class that defines only __eq__?The decorator raises `ValueError` with the message *"must define at least one ordering operation: < > <= >="*. It happens at decoration time — that is, when the module containing the class is imported — so the failure is loud and immediate rather than appearing on the first `<` at runtime.
- Does functools.total_ordering overwrite an ordering operator the class already defines?No. It inspects the four ordering slots, treats any that still holds the inherited default from `object` as missing, and fills in only those. A method you wrote yourself always wins, so you can decorate a class and still hand-write one operator for speed or for special semantics.
- If a class is a simple record ordered by its fields, is total_ordering the best tool?Often not. A dataclass declared with `order=True` generates all four ordering methods comparing the fields as a tuple, in field-declaration order, with no hand-written `__lt__`. Reach for `total_ordering` when the ordering rule is genuinely custom — a version-precedence rule, a domain-specific ranking — rather than plain field order.
Give a surveyor one measured baseline and a known reference point and they can derive every other bearing in the map; give them nothing to start from and they refuse the job outright.
saying these in an interview costs you the question
- Claiming it generates all six operators from nothing
- Thinking __ne__ must be written by hand as well
- Believing it overrides operators the class already defines
- Assuming the derived operators are as fast as hand-written ones
- Applying it to a partially ordered, set-like type
- Calling it a function decorator rather than a class decorator