Python 2.x tiene dos formas de sobrecargar los operadores de comparación, __cmp__
o los "operadores de comparación enriquecidos" como __lt__
. Se dice que se prefieren las abundantes sobrecargas de comparación, pero ¿por qué es así?
Los operadores de comparación ricos son más simples de implementar cada uno, pero debe implementar varios de ellos con una lógica casi idéntica. Sin embargo, si puede usar el orden integrado cmp
y de tuplas, entonces se __cmp__
vuelve bastante simple y cumple con todas las comparaciones:
class A(object):
def __init__(self, name, age, other):
self.name = name
self.age = age
self.other = other
def __cmp__(self, other):
assert isinstance(other, A) # assumption for this example
return cmp((self.name, self.age, self.other),
(other.name, other.age, other.other))
Esta simplicidad parece satisfacer mis necesidades mucho mejor que sobrecargar las 6 (!) De las ricas comparaciones. (Sin embargo, puede reducirlo a "solo" 4 si confía en el "argumento intercambiado" / comportamiento reflejado, pero eso da como resultado un aumento neto de complicaciones, en mi humilde opinión).
¿Hay problemas imprevistos de los que deba ser consciente si solo sobrecargo __cmp__
?
Entiendo la <
, <=
, ==
, etc. operadores pueden ser sobrecargados para otros fines, y puede devolver cualquier objeto que les gusta. No estoy preguntando sobre los méritos de ese enfoque, sino solo sobre las diferencias al usar estos operadores para comparaciones en el mismo sentido en que se refieren a los números.
Actualización: como señaló Christopher , cmp
está desapareciendo en 3.x. ¿Existen alternativas que faciliten la implementación de comparaciones como las anteriores __cmp__
?
fuente
Respuestas:
Sí, es fácil implementar todo en términos de, por ejemplo,
__lt__
con una clase mixin (o una metaclase, o un decorador de clases si su gusto es así).Por ejemplo:
Ahora su clase puede definir
__lt__
heredar solo y multiplicar de ComparableMixin (después de cualquier otra base que necesite, si corresponde). Un decorador de clases sería bastante similar, simplemente insertando funciones similares como atributos de la nueva clase que está decorando (el resultado podría ser microscópicamente más rápido en tiempo de ejecución, con un costo igualmente mínimo en términos de memoria).Por supuesto, si su clase tiene alguna forma particularmente rápida de implementar (por ejemplo)
__eq__
y__ne__
, debe definirlas directamente para que las versiones de mixin no se utilicen (por ejemplo, ese es el casodict
); de hecho,__ne__
podría definirse para facilitar Eso es todo:pero en el código anterior quería mantener la agradable simetría de solo usar
<
;-). En cuanto a por qué__cmp__
tenía que ir, ya que lo hizo tener__lt__
y amigos, ¿por qué mantener a la otra, diferente manera de hacer exactamente lo mismo que en todo? Es un peso muerto en cada tiempo de ejecución de Python (Classic, Jython, IronPython, PyPy, ...). El código que definitivamente no tendrá errores es el código que no está allí, de ahí el principio de Python de que idealmente debería haber una forma obvia de realizar una tarea (C tiene el mismo principio en la sección "Espíritu de C" de el estándar ISO, por cierto).Esto no quiere decir que vayamos fuera de nuestro camino para prohibir las cosas (por ejemplo, cerca de la equivalencia entre mixins y decoradores de clase para algunos usos), pero definitivamente hace media que no nos gusta llevar alrededor de código en los compiladores y / o tiempos de ejecución que existen de forma redundante solo para admitir múltiples enfoques equivalentes para realizar exactamente la misma tarea.
Edición adicional: en realidad, hay una manera aún mejor de proporcionar comparación Y hash para muchas clases, incluido eso en la pregunta: un
__key__
método, como mencioné en mi comentario a la pregunta. Como nunca llegué a escribir el PEP para él, actualmente debe implementarlo con un Mixin (& c) si le gusta:Es un caso muy común que las comparaciones de una instancia con otras instancias se reduzcan a comparar una tupla para cada una con unos pocos campos, y luego, el hash debe implementarse exactamente sobre la misma base. El
__key__
método especial aborda esa necesidad directamente.fuente
TypeError: Cannot create a consistent method resolution order (MRO) for bases object, ComparableMixin
cuando pruebo esto en Python 3. Vea el código completo en gist.github.com/2696496functools.total_ordering
lugar de construir el suyo propioComparableMixim
. Como se sugiere en la respuesta de jmagnusson<
para implementar__eq__
en Python 3 es una idea bastante mala, debido aTypeError: unorderable types
.Para simplificar este caso, hay un decorador de clases en Python 2.7 + / 3.2 +, functools.total_ordering , que se puede usar para implementar lo que sugiere Alex. Ejemplo de los documentos:
fuente
total_ordering
aunque no se implementa__ne__
, ¡así que ten cuidado!__ne__
. pero eso es porque__ne__
tiene una implementación predeterminada a la que delega__eq__
. Así que no hay nada que vigilar aquí.Esto está cubierto por PEP 207 - Comparaciones enriquecidas
Además,
__cmp__
desaparece en Python 3.0. (Tenga en cuenta que no está presente en http://docs.python.org/3.0/reference/datamodel.html pero está en http://docs.python.org/2.7/reference/datamodel.html )fuente
(Editado el 17/6/17 para tener en cuenta los comentarios).
Probé la respuesta de mezcla comparable anterior. Me metí en problemas con "Ninguno". Aquí hay una versión modificada que maneja comparaciones de igualdad con "Ninguno". (No vi ninguna razón para molestarme con las comparaciones de desigualdad con Ninguno por carecer de semántica):
fuente
self
podría ser el singletonNone
deNoneType
y al mismo tiempo implementar tuComparableMixin
? Y, de hecho, esta receta es mala para Python 3.self
será no serNone
, de manera que la rama puede ir por completo. No lo usetype(other) == type(None)
; simplemente useother is None
. En lugar de especial-carcasaNone
, prueba si el otro tipo es una instancia de la clase deself
, y devolver laNotImplemented
singleton si no:if not isinstance(other, type(self)): return NotImplemented
. Haga esto para todos los métodos. Python entonces puede darle al otro operando la oportunidad de proporcionar una respuesta en su lugar.Inspirado por Alex Martelli de
ComparableMixin
yKeyedMixin
respuestas, se me ocurrió la siguiente mixin. Le permite implementar un solo_compare_to()
método, que utiliza comparaciones basadas en claves similares aKeyedMixin
, pero permite que su clase elija la clave de comparación más eficiente según el tipo deother
. (Tenga en cuenta que este mixin no ayuda mucho para los objetos que se pueden probar para la igualdad pero no el orden).fuente