Comparable classes in Python 3

What is the standard way to create a class comparable in Python 3? (For example, by id.)

+6
source share
5 answers

For a full set of comparison functions, I used the following mixin, which you could add, for example, to 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 mixin above, you need to implement the _cmpkey () method, which returns the key of objects that can be compared, similar to the key () function used in sorting. An implementation might 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 error . This is fixed in Python 3.4, but often you also need to support older versions of Python.

+4
source

sort is required only __lt__ .

functools.total_ordering (since version 2.7 / 3.2) is a decorator that provides all comparison operators, so you don’t need to write everything yourself.

By default, classes are hashed, and this uses their id() ; I'm not sure why you want to order classes by their id() , if you just don't want the order to be stable.

+6
source

Not sure if this is complete, but you want to determine:

 __eq__, __gt__, __ge__, __lt__, __le__ 

As agf said I was missing:

 __ne__ 
0
source

You said you were trying to do this:

 max((f(obj), obj) for obj in obj_list)[1] 

You should just do this:

 max(f(obj) for obj in obj_list) 

EDIT: Or as gnibbler said: max(obj_list, key=f)

But you told gnibbler that you need a reference to the max object. I think this is the 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 can let it throw an exception rather than returning it if you try to find max_obj () from an empty list.

0
source

I just thought of a really hacky way to do this. This is in the same vein as you originally tried 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 this, so here is something less short, which 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 can use this one-line key if obj_list always an indexable object, such as a list:

 obj_list[max((f(obj), i) for i, obj in enumerate(obj_list))[1]] 

This has the advantage that if there are several objects for which f(obj) returns the same value, you know which one you will get: the one that has the highest index, i.e. last on the list. If you need the earliest from the list, you can do this with a key function.

0
source

Source: https://habr.com/ru/post/894084/


All Articles