21

In Python3, the functools.total_ordering decorator allows one to only overload __lt__ and __eq__ to get all 6 comparison operators.

I don't get why one has to write two operators when one would be enough, namely __le__ or __ge__, and all others would be defined accordingly :

a < b   <=>   not (b <= a)
a > b   <=>   not (a <= b)
a == b   <=>   (a <= b) and (b <= a)
a != b   <=>   (a <= b) xor (b <= a)

Is that just because xor operator does not exists natively?

Guillaume Lemaître
  • 1,230
  • 13
  • 18

1 Answers1

31

The documentation states you must define one of __lt__(), __le__(), __gt__(), or __ge__(), but only should supply an __eq__() method.

In other words, the __eq__ method is optional.

The total_ordering implementation does not require you to specify an __eq__ method; it only tests for the __lt__(), __le__(), __gt__(), or __ge__() methods. It supplies up to 3 missing special methods based of one of those 4.

You can't base the order on just __le__ or __ge__ because you can't assume that you can swap a and b; if b is a different type b.__le__ might not be implemented and so your a < b <=> not (b <= a) map can't be guaranteed. The implementation uses (a <= b) and (a != b) if __le__ is not defined but __lt__ has been.

The full table of mappings is:

comparison available alternative
a > b a < b (not a < b) and (a != b)
a <= b (not a <= b)
a >= b (a >= b) and (a != b)
a <= b a < b (a < b) or (a == b)
a > b (not a > b)
a >= b (not a >= b) or (a == b)
a < b a <= b (a <= b) and (a != b)
a > b (not a > b) and (a != b)
a >= b (not a >= b)
a >= b a < b (not a < b)
a <= b (not a <= b) or (a == b)
a > b (a > b) or (a == b)

The __eq__ method is optional because the base object object defines one for you; two instances are considered equal only if they are the same object; ob1 == ob2 only if ob1 is ob2 is True. See the do_richcompare() function in object.c; remember that the == operator in the code there is comparing pointers.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Is there a way to indicate to to `total_ordering` (or another method) that you only want comparison to work between objects of the same type, so you can deduce that `a < b` implies `b < a` is also available, and then deduce all 6 comparison operator from just one supplied operator implementation? – dylanmorroll Feb 21 '23 at 10:19
  • @dylanmorroll: read my answer carefully, it explains that it does what you want already: *The total_ordering implementation [...] tests for the `__lt__()`, `__le__()`, `__gt__()`, or `__ge__()` methods. **It supplies up to 3 missing special methods based of one of those 4.*** There are only 5 comparison methods, not six, by the way. – Martijn Pieters Feb 21 '23 at 10:47
  • @dylanmorroll: you can return `NotImplemented` from your `__lt__` implementation if the comparison is against a different type you don't support, and total_ordering will do the right thing. – Martijn Pieters Feb 21 '23 at 10:53
  • Perhaps I should have clarified, I meant to get all the comparison operators including a proper equality (rather than the implicit equality which is an identity check). Ah yes I was including not equal to in my counting of 6 but I guess negation isn't a new operator. – dylanmorroll Feb 24 '23 at 08:56
  • @dylanmorroll then provide an `__eq__` implantation and make use of the `NotImplemented` signalling object. – Martijn Pieters Feb 25 '23 at 01:35