Clases comparables en Python 3

¿Cuál es la forma estándar de hacer una clase comparable en Python 3? (Por ejemplo, por id.)

Para un conjunto completo de funciones de comparación, he usado la siguiente combinación, que podría poner, por ejemplo, una mezcla en su módulo.

 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) 

Para utilizar la combinación anterior, debe implementar un método _cmpkey () que devuelva una clave de objetos que se puedan comparar, de manera similar a la función key () que se usa al ordenar. La implementación podría verse así:

 >>> 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] 

La razón por la que uso esto en lugar de la receta total_ordering es este error . Se solucionó en Python 3.4, pero a menudo también es necesario admitir versiones anteriores de Python.

sort solo necesita __lt__ .

functools.total_ordering (a partir de 2.7 / 3.2) es un decorador que proporciona todos los operadores de comparación para que no tenga que escribirlos todos usted mismo.

De forma predeterminada, las clases son hashable, y esto utiliza su id() ; No estoy seguro de por qué querría ordenar las clases por su id() menos que solo quisiera que el orden fuera estable.

No estoy seguro si esto está completo, pero querrías definir:

 __eq__, __gt__, __ge__, __lt__, __le__ 

Como dijo una vez más, me falta:

 __ne__ 

Usted dijo que está tratando de hacer esto:

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

Simplemente debes hacer esto:

 max(f(obj) for obj in obj_list) 

EDITAR: O como gnibbler dijo: max(obj_list, key=f)

Pero le dijo a gnibbler que necesita una referencia de objeto al objeto max. Creo que esto es lo más simple:

 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) 

Por supuesto, es posible que desee dejar que genere una excepción en lugar de devolver ninguna si intenta encontrar el max_obj () de una lista vacía.

Solo pensé en una manera realmente hackish de hacerlo. Esto está en el mismo espíritu que lo que originalmente intentabas hacer. No requiere agregar ninguna función al objeto de clase; Funciona para cualquier clase.

 max(((f(obj), obj) for obj in obj_list), key=lambda x: x[0])[1] 

Realmente no me gusta eso, así que aquí hay algo menos conciso que hace lo mismo:

 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] 

O bien, podría usar esta línea obj_list si obj_list es siempre un objeto indexable como una lista:

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

Esto tiene la ventaja de que si hay varios objetos tales que f(obj) devuelve un valor idéntico, sabrá cuál obtendrá: el que tenga el índice más alto, es decir, el más reciente en la lista. Si deseaba el más antiguo de la lista, podría hacerlo con una función clave.