What is the standard way of making a class comparable in Python 3? (For example, by id.)
-
I mean using `id(obj)` as the key for sorting. – Neil G Aug 02 '11 at 04:51
-
2Uh, why would you want to sort by the arbitrarily assigned memory address of your class? – Tim Pietzcker Aug 02 '11 at 04:58
-
1sure you __could__, but would it make sense to order things by their memory locations? – jcomeau_ictx Aug 02 '11 at 04:58
-
1@Tim: Because I am doing: `max((f(obj), obj) for obj in obj_list)[1]` to get the obj with the highest value according to `f`. Python 3 complains that obj is not comparable. I don't really care how it's compared. – Neil G Aug 02 '11 at 04:59
-
I don't get it. How can you want to find the "highest value" while at the same time not caring how a comparison is done? – Tim Pietzcker Aug 02 '11 at 05:04
-
4Surely you mean to say `max(obj_list, key=f)` instead of that monstrosity – John La Rooy Aug 02 '11 at 05:07
-
@Tim I want the `obj` such that `f(obj)` is maximized. See http://stackoverflow.com/questions/5098580/implementing-argmax-in-python – Neil G Aug 02 '11 at 05:07
-
1@gnibbler: Thanks!! But what if I want both the `obj` as well as `f(obj)`? – Neil G Aug 02 '11 at 05:08
-
Just call `f` again on the result. It's probably more efficient than creating all those tuples – John La Rooy Aug 02 '11 at 05:22
5 Answers
To make classes comparable, you only need to implement __lt__
and decorate the class with functools.total_ordering
. You should also provide an __eq__
method if possible. This provides the rest of the comparison operators so you don't have to write any of them yourself.

- 171,228
- 44
- 289
- 238
-
3+1 Just want to mention for anyone else searching this decorator is available as `functools.total_ordering`. – Neil G Aug 02 '11 at 05:09
-
@Neil G : + everyone else: Also be aware that that decorator has bugs, and shouldn't be used if you ever expect anyone to compare your class with any other class http://bugs.python.org/issue10042 My preferred method is a mixin: http://python3porting.com/preparing.html#comparatively-tricky – Lennart Regebro Aug 02 '11 at 07:37
-
@Lennart: Please add an answer. I did notice that mixin. Why wasn't it added to the Python standard library like the decorator? – Neil G Aug 02 '11 at 13:12
-
-
-
4
-
The [docs](https://docs.python.org/3/library/functools.html) for total_ordering say you should also implement `__eq__`. – Entropic Thunder May 08 '21 at 22:17
-
1
-
I used ChatGPT to generate the rest of the methods, because there's a note in the documentation saying that `total_ordering` has a cost of slower execution, and performance is relevant for what I'm doing specifically. If that's not the case then I'd say `total_ordering `should be fine – netotz Aug 23 '23 at 16:51
For a full set of comparison functions I have used the following mixin, which you could put in say for example a mixin.py in your module.
class ComparableMixin(object):
def _compare(self, other, method):
try:
return method(self._cmpkey(), other._cmpkey())
except (AttributeError, TypeError):
# _cmpkey not implemented, or return different type,
# so I can't compare with "other".
return NotImplemented
def __lt__(self, other):
return self._compare(other, lambda s, o: s < o)
def __le__(self, other):
return self._compare(other, lambda s, o: s <= o)
def __eq__(self, other):
return self._compare(other, lambda s, o: s == o)
def __ge__(self, other):
return self._compare(other, lambda s, o: s >= o)
def __gt__(self, other):
return self._compare(other, lambda s, o: s > o)
def __ne__(self, other):
return self._compare(other, lambda s, o: s != o)
To use the mixin above you need to implement a _cmpkey() method that returns a key of objects that can be compared, similar to the key() function used when sorting. The implementation could look like this:
>>> from .mixin import ComparableMixin
>>> class Orderable(ComparableMixin):
...
... def __init__(self, firstname, lastname):
... self.first = firstname
... self.last = lastname
...
... def _cmpkey(self):
... return (self.last, self.first)
...
... def __repr__(self):
... return "%s %s" % (self.first, self.last)
...
>>> sorted([Orderable('Donald', 'Duck'),
... Orderable('Paul', 'Anka')])
[Paul Anka, Donald Duck]
The reason I use this instead of the total_ordering recipe is this bug. It's fixed in Python 3.4, but often you need to support older Python versions as well.

- 167,292
- 41
- 224
- 251
-
It's worth pointing out to anyone finding this answer that the `mixin` module is a hypothetical module one could define — it's not in the Python standard library. – Neil G Aug 02 '11 at 14:39
-
The bug has now been fixed. Would you mind updating your answer or adding a new one? – Neil G May 21 '15 at 03:03
-
Thank you! So, in Python >3.4, you would recommend applying the decorator `functools.total_ordering`? – Neil G May 21 '15 at 08:55
-
I haven't looked at the final solution they put into 3.4, so I can't tell for sure, but it's probably fine. – Lennart Regebro May 21 '15 at 09:52
Not sure if this is complete, but you'd want to define:
__eq__, __gt__, __ge__, __lt__, __le__
As agf said, I'm missing:
__ne__

- 1
- 1

- 37,688
- 6
- 92
- 107
-
Isn't there a way to have most of these automatically defined with a mixin? – Neil G Aug 02 '11 at 04:47
-
-
See the decorator recipe in my answer. Also, the other comparison is `__ne__`, not equal. – agf Aug 02 '11 at 04:50
-
@Ignacio: Perhaps if one is defined, it could use it to define the rest? – Neil G Aug 02 '11 at 04:55
-
never used mixins, sorry can't help there. I'm still a little unsure of myself with Python3 as well, mostly continuing using Python2. – jcomeau_ictx Aug 02 '11 at 04:57
You said you are trying to do this:
max((f(obj), obj) for obj in obj_list)[1]
You should simply do this:
max(f(obj) for obj in obj_list)
EDIT: Or as gnibbler said: max(obj_list, key=f)
But you told gnibbler you need an object reference to the max object. I think this is simplest:
def max_obj(obj_list, max_fn):
if not obj_list:
return None
obj_max = obj_list[0]
f_max = max_fn(obj)
for obj in obj_list[1:]:
if max_fn(obj) > f_max:
obj_max = obj
return obj_max
obj = max_obj(obj_list)
Of course you might want to let it raise an exception rather than return none if you try to find the max_obj() of an empty list.

- 74,789
- 21
- 92
- 117
-
-
Yes, I didn't quite grok what you were doing when I wrote the first version. I think I have it now. – steveha Aug 02 '11 at 05:42
-
Did you really need to post two separate answers? They're both doing the same thing, and both are way less clear than the original `max((f(obj), obj) for obj in obj_list)` – agf Aug 02 '11 at 05:47
-
Do you consider it some sort of breach of etiquette to post two completely different answers as two separate answers? And do you really think they are doing the same thing? One calls max() with a key, the other is a for loop; how are they doing the same thing? And I am not sure I agree my solutions are less clear. By the way, you left off the `[1]` from his answer, which was tricky enough it fooled me the first time I read it. – steveha Aug 02 '11 at 05:58
I just thought of a really hackish way to do it. This is in the same spirit as what you were originally trying to do. It does not require adding any functions to the class object; it works for any class.
max(((f(obj), obj) for obj in obj_list), key=lambda x: x[0])[1]
I really don't like that, so here's something less terse that does the same thing:
def make_pair(f, obj):
return (f(obj), obj)
def gen_pairs(f, obj_list):
return (make_pair(f, obj) for obj in obj_list)
def item0(tup):
return tup[0]
def max_obj(f, obj_list):
pair = max(gen_pairs(f, obj_list), key=item0)
return pair[1]
Or, you could use this one-liner if obj_list
is always an indexable object like a list:
obj_list[max((f(obj), i) for i, obj in enumerate(obj_list))[1]]
This has the advantage that if there are multiple objects such that f(obj)
returns an identical value, you know which one you will get: the one with the highest index, i.e. the latest one in the list. If you wanted the earliest one in the list, you could do that with a key function.

- 74,789
- 21
- 92
- 117