Hacer que una clase definida por el usuario de Python se pueda ordenar, tener hash

82

¿Qué métodos deben anularse / implementarse al hacer que las clases definidas por el usuario se puedan ordenar y / o tener hash en Python?

¿Cuáles son las trampas a tener en cuenta?

Tipo I dir({})en mi intérprete para obtener una lista de métodos de dicts incorporado. De ellos, supongo que necesito implementar algún subconjunto de

['__cmp__', '__eq__', '__ge__', '__gt__', '__hash__', '__le__', '__lt__', '__ne__']

¿Hay alguna diferencia en qué métodos deben implementarse para Python3 en comparación con Python2?

Matt Fenwick
fuente
3
Buena discusión aquí: stackoverflow.com/q/1061283/641766 . La diferencia entre Python 2.xy 3.x es que __cmp__se eliminó.
zeekay

Respuestas:

88

Casi publico esto como un comentario a las otras respuestas, pero en realidad es una respuesta en sí misma.

Para que sus elementos se puedan ordenar, solo necesitan implementarlos __lt__. Ese es el único método utilizado por la ordenación integrada.

Las otras comparaciones o functools.total_orderingsolo son necesarias si realmente desea utilizar los operadores de comparación con su clase.

Para hacer que sus artículos sean hash, implemente __hash__como otros señalaron. También debe implementar __eq__de una manera compatible: los elementos que son equivalentes deben usar el mismo hash.

agf
fuente
Entonces, ¿una implementación deficiente de __lt__Python podría hacer que se clasifique de manera impredecible? (por ejemplo, si x .__ lt __ (y) y y .__ lt __ (x))
Matt Fenwick
3
No sé sobre "impredeciblemente", será coherente si se alimenta exactamente con la misma entrada, pero un orden de entrada diferente podría hacer que los elementos diferentes estén en un orden diferente. Sí, si implementa incorrectamente la comparación utilizada para ordenar, Python se ordenará incorrectamente. Recomendaría una __key__función que convierta la instancia en una tupla, luego haga que tanto __lt__( self.__key__() < other.__key__()) como __hash__( hash(self.__key__())) la usen.
agf
19

No hay ninguna diferencia entre Python 2 y 3.

Para ordenar:

Debe definir métodos de comparación. Esto hace que sus artículos se puedan ordenar. Generalmente, no debería preferir __cmp__().

Yo suelo usar el decorador functools.total_ordering.

functools.total_ordering (cls) Dada una clase que define uno o más métodos de ordenación de comparación ricos, este decorador de clases proporciona el resto. Esto simplifica el esfuerzo que implica especificar todas las posibles operaciones de comparación enriquecidas:

La clase debe definir uno de __lt__(), __le__(), __gt__(), o __ge__(). Además, la clase debe proporcionar un __eq__()método.

Debe tener cuidado de que sus métodos de comparación no tengan efectos secundarios. (cambiar cualquiera de los valores del objeto)

Para hash:

Debes implementar el __hash__()método. Creo que la mejor forma es regresar hash(repr(self)), para que tu hash sea único.

utdemir
fuente
Para obtener un ejemplo de functools.total_orderingla documentación, consulte aquí .
Evgeni Sergeev
3

Hay algunas formas de marcar su objeto de forma ordenable. Primero: comparación rica, definida por un conjunto de funciones:

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

También es posible definir solo una función:

object.__cmp__(self, other)

Y el último debe definirse si desea definir una __hash__función personalizada . Ver el doc .

Bodnarchuk romano
fuente
6
En Python 3, "[...] el __cmp__()método especial ya no es compatible", consulte la sección correspondiente aquí .
Evgeni Sergeev
-3

El __lt__(self,other)método de implementación es la respuesta para hacer que su clase se pueda ordenar.
Se puede utilizar no solo para el método integrado sorted(iterable), sino también para la cola de prioridad a través del heapqmódulo.

Además, no me gusta el diseño de Python, ¡muchos '__ge__', '__gt__', '__le__', '__lt__', '__ne__'métodos no son intuitivos en absoluto !
Como contraste, Java Interface Comparable<T> (ver java doc ) devuelve un entero negativo, cero o un entero positivo ya que este objeto es menor, igual o mayor que el objeto especificado, ¡que es directo y amigable !

yichudu
fuente
9
La mayor parte de su respuesta cubre su opinión (que no debería ser parte de la respuesta).
skyking
@skyking ... aunque no estoy de acuerdo con las opiniones en esta respuesta en particular, creo que la opinión (con datos investigados y útiles relevantes para la pregunta) es muy valiosa. El error en esta respuesta de opinión es no apoyar la opinión con datos relevantes. Pero comprender las preferencias ayuda a las personas a tomar decisiones y, por lo tanto, es muy útil. Aquí, la respuesta está mal formada porque no hay un código Python útil y procesable para ilustrar una forma pitónica de codificar según las preferencias del autor.
Andrew