The following mathematical relationships between comparison relations (=, ≠, <, >, ≤ and ≥) are always valid and therefore implemented by default in Python (except for the 2 union relationships, which seems arbitrary and is the reason of this post):
- 2 complementary relationships: "= and ≠ are each other’s complement";
- 6 converse relationships*: "= is the converse of itself", "≠ is the converse of itself", "< and > are each other’s converse", and "≤ and ≥ are each other’s converse";
- 2 union relationships: "≤ is the union < and =" and "≥ is the union of > and =".
The following relationships between comparison relations are only valid for total orders and therefore not implemented by default in Python (but users can conveniently implement them when they are valid with the class decorator functools.total_ordering provided by the Python standard library):
- 4 complementary relationships: "< and ≥ are each other’s complement" and "> and ≤ are each other’s complement".
Why is Python only lacking the 2 union relationships above ("≤ is the union < and =" and "≥ is the union of > and =")?
It should provide a default implementation of __le__ in terms of __lt__ and __eq__, and a default implementation of __ge__ in terms of __gt__ and __eq__, like these (but probably in C for performance, like __ne__):
def __le__(self, other):
result_1 = self.__lt__(other)
result_2 = self.__eq__(other)
if result_1 is not NotImplemented and result_2 is not NotImplemented:
return result_1 or result_2
return NotImplemented
def __ge__(self, other):
result_1 = self.__gt__(other)
result_2 = self.__eq__(other)
if result_1 is not NotImplemented and result_2 is not NotImplemented:
return result_1 or result_2
return NotImplemented
The 2 union relationships are always valid so these default implementations would free users from having to provide them all the time (like here).
Here is the paragraph of the Python documentation which states explicitly that the 2 union relationships are not currently implemented by default (bold emphasis mine):
By default,
__ne__()delegates to__eq__()and inverts the result unless it isNotImplemented. There are no other implied relationships among the comparison operators, for example, the truth of(x<y or x==y)does not implyx<=y.
* Converse relationships are implemented in Python through the NotImplemented protocol.
Why exactly this decision was made only the original author knows, but given these hints from the manual reasons can be inferred:
Pair this with Python's mantra of explicit is better than implicit, the following reasoning should be satisfactory:
Deriving
__ne__from__eq__is virtually free, it's just the operationnot o.__eq__(other), i.e. inverting a boolean.However, deriving
__le__from the union of__lt__and__eq__means that both methods need to be called, which could be a potentially large performance hit if the comparison done is complex enough, especially compared to an optimised single__le__implementation. Python lets you opt-into this convenience-over-performance explicitly by using thetotal_orderingdecorator, but it won't implicitly inflict it on you.You could also argue for explicit errors if you attempt to do unimplemented comparisons instead of implicitly derived comparisons which you didn't implement and which may create subtle bugs, depending on what you meant to do with your custom classes. Python won't make any guesses for you here and instead leave it up to you to either explicitly implement the comparisons you want, or to again explicitly opt-into the derived comparisons.