What does the `functools.total_ordering` class decorator generate?
answer
- Four ordering operators, one rule
- A class decorator from the standard library
- Supply one, receive the rest
- Equality is required too
- Convenience paid for in call overhead
basics
~10 sIt fills in the rich-comparison methods a class is missing. Define __eq__ plus any one of __lt__, __le__, __gt__ or __ge__, and the decorator derives the other three ordering methods from that pair.
solid answer
~50 sPython's four ordering operators map to `__lt__`, `__le__`, `__gt__` and `__ge__`, and defining `<` gives you nothing for `>=`. `functools.total_ordering` closes that gap: it inspects the class, takes whichever ordering method you supplied as the root, and synthesises the rest in terms of it and `__eq__`. If the class supplies no ordering method at all it raises `ValueError` at decoration time, not at first comparison. The cost is speed — each derived operator is an extra Python-level call into the root method, so a type compared in a hot loop is better off defining all four by hand. The derived methods also propagate `NotImplemented` from the root, so comparing against an unrelated type still fails loudly instead of returning a wrong answer. For a plain record type, `dataclasses.dataclass(order=True)` generates all four directly and is usually the better default.
code
python · 20 linesimport functools
@functools.total_ordering
class Batch:
def __init__(self, rows):
self.rows = rows
def __eq__(self, other):
if not isinstance(other, Batch):
return NotImplemented
return self.rows == other.rows
def __lt__(self, other):
if not isinstance(other, Batch):
return NotImplemented
return self.rows < other.rows
print(Batch(6800) >= Batch(120), Batch(6800) <= Batch(6800))go deeper
Know that < and >= are separate methods and that defining one does not give you the other. Recognising the decorator's name and what it saves you is plenty at this level.
State the exact requirement — __eq__ plus one of the four ordering methods — and that the decorator supplies the rest, raising ValueError at decoration time if you supplied none.
Bring the tradeoff and the alternatives: derived operators cost an extra call, a dataclass with ordering may be the better shape, and a key function keeps an ordering opinion out of the type entirely.
Decide where ordering belongs at all. A type with a single natural order can carry it; a type ordered differently by different callers should stay unordered and let call sites pass keys, which avoids a library-wide default nobody agrees on.
## The problem it solves Python's comparison operators are not derived from one another. `<`, `<=`, `>` and `>=` each dispatch to their own method — `__lt__`, `__le__`, `__gt__`, `__ge__` — and the interpreter will not infer `>=` from `<`. A class that defines only `__lt__` therefore sorts fine, because sorting only ever uses `<`, but raises `TypeError: '>=' not supported between instances of ...` the moment someone writes a threshold check. Writing all four by hand is four near-identical bodies, and each is a place to get an inequality backwards. `functools.total_ordering` is a class decorator that does the derivation for you. ## What you must supply, and what you get The contract is: define `__eq__`, and define at least one of `__lt__`, `__le__`, `__gt__` or `__ge__`. The decorator picks whichever ordering methods the class actually provides, uses one as the root, and adds the ones that are missing, each expressed in terms of the root plus equality. Methods the class already defines are left alone. ```python import functools @functools.total_ordering class Batch: def __init__(self, rows): self.rows = rows def __eq__(self, other): if not isinstance(other, Batch): return NotImplemented return self.rows == other.rows def __lt__(self, other): if not isinstance(other, Batch): return NotImplemented return self.rows < other.rows print(Batch(6800) >= Batch(120)) # True, from the derived __ge__ ``` If you forget the ordering method, you find out immediately: decorating a class that has none raises `ValueError: must define at least one ordering operation: < > <= >=` while the module is being imported. That is a deliberate design choice — a decoration-time failure instead of a mystery `TypeError` in production. ## Mixed types stay honest A derived operator is only as good as the root, and the derivation is written so that a root returning `NotImplemented` makes the derived operator return `NotImplemented` too. That is why the example above type-checks its operand and returns `NotImplemented` rather than `False` for a foreign type: comparing a `Batch` with an unrelated object then produces the proper `TypeError` from the interpreter instead of an answer that is quietly wrong. A root that returns `False` for anything it does not recognise would make every derived operator lie. ## The costs The convenience is not free. A hand-written `__ge__` is one method call that does one comparison; a derived `__ge__` is a method call that calls the root and combines its result, so ordering-heavy code pays roughly a doubled call cost on the derived operators. For a type that is compared millions of times — sort keys in a large pipeline, priority-queue entries — write the four methods out; for a domain object compared occasionally, the decorator is the clearer code and the difference is unmeasurable. The second cost is indirection when reading a traceback: the frame you land in belongs to the decorator's generated method, not to code in your file. ## The alternatives Before reaching for the decorator, ask what the class is. For a record of fields, `dataclasses.dataclass(order=True)` generates `__lt__`, `__le__`, `__gt__` and `__ge__` directly, comparing the fields as a tuple in declaration order, and returning `NotImplemented` for other classes; no decorator stacking required. If your ordering is really "compare these attributes in this order", that is both faster and more obvious than deriving from a root. If you only ever need to sort, you may need nothing at all: `sorted` and `list.sort` use `<` exclusively, so a single `__lt__` — or a `key` function, which is usually the better answer — is sufficient. And if the ordering rule belongs to the caller rather than the type, a `key` function keeps the class free of an opinion it should not carry. ## Answering it well Name the requirement precisely (`__eq__` plus one ordering method), say the decorator adds the missing three, mention the decoration-time `ValueError`, and finish with the tradeoff: convenience and one source of truth for the ordering rule, paid for with an extra call per derived comparison.
- Why might you write all four ordering methods by hand instead of using the decorator?Speed and traceability. Each derived operator adds a Python-level call into the root method and a combination step, which shows up in code that compares objects in a tight sort or a priority queue. Hand-written methods also put the failing frame in your own file. For an object compared occasionally, the decorator is the better tradeoff.
- Which comparison method does `sorted()` actually require on the elements?Only `__lt__`. Python's sort is defined entirely in terms of `<`, so a class with a single `__lt__` sorts correctly without any other comparison method. That is also why sorting can succeed on a type where `>=` raises — and why a `key` function is often the cleaner way to express an ordering that belongs to the caller.
- How does `dataclasses.dataclass(order=True)` differ from `functools.total_ordering`?The dataclass generates all four ordering methods directly, comparing the fields as a tuple in declaration order and returning `NotImplemented` against other classes. `total_ordering` derives the missing operators from ordering logic you wrote yourself. Use the dataclass when field order *is* the rule, the decorator when the rule is custom.
saying these in an interview costs you the question
- Thinks the decorator also generates `__eq__`
- Believes it makes comparisons faster
- Applies it to a class with no ordering method
- Assumes `sorted()` needs all four ordering methods
- Expects it to overwrite methods the class already defines
- Returns `False` instead of `NotImplemented` for foreign types