Python ha permitido comparar objetos desde sus primeras versiones. Para describir lo que significa "mayor" o "igual" para tus objetos, se crearon métodos de comparación especiales (eq, ne, lt, le, gt, ge). Con la llegada de PEP 207 (Python 2.1+) se implementó un protocolo de comparación unificado para que los programadores pudieran controlar explícitamente el comportamiento durante las comparaciones.
Los tipos estándar (int, str) se comparan "por valor", pero las clases de usuario se comparan por "identidad" (is) por defecto. Si no se implementan o se implementan incorrectamente los métodos mágicos de comparación, la comparación de tus objetos dará un comportamiento inesperado al trabajar con colecciones, ordenaciones, y el uso de set y dict, y a veces resultará en errores de tipo.
Es necesario definir explícitamente los métodos de comparación en tu clase. Más comúnmente se implementa eq (igualdad) y lt (menor), y los demás métodos se pueden obtener automáticamente mediante el decorador functools.total_ordering.
Ejemplo de código:
from functools import total_ordering @total_ordering class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if not isinstance(other, Point): return NotImplemented return self.x == other.x and self.y == other.y def __lt__(self, other): if not isinstance(other, Point): return NotImplemented return (self.x, self.y) < (other.x, other.y) # Uso: p1 = Point(1, 2) p2 = Point(1, 3) print(p1 < p2) # True print(p1 == p2) # False
Características clave:
¿Se puede implementar solo un método de comparación (eq o lt), y todo funcionará?
No. Al implementar solo eq, proporcionarás una comparación correcta para igualdad, pero los operadores <, >, <=, >= se comportarán de forma estándar (usualmente a través de comparaciones de identidad de objetos). Para un comportamiento completo, se debe implementar un conjunto suficiente de métodos, o utilizar functools.total_ordering.
¿Por qué compare(x, y) no existe en Python, como en otros lenguajes?
Python heredó la idea de métodos mágicos especiales para cada operador, y la función cmp fue eliminada a partir de Python 3. Para comparar objetos en general se utilizan métodos de comparación rica (lt, eq, etc.), y para ordenar se utiliza el parámetro key en las funciones sort/sorted.
¿Qué sucede si se compara un objeto con un tipo para el cual no se ha implementado el método necesario?
Si en eq o en otros métodos se retorna NotImplemented para tipos no soportados, Python automáticamente intentará el método inverso en el segundo operando o retornará False. Si este caso no se maneja, puede haber un TypeError o un comportamiento completamente incorrecto.
Ejemplo de código:
class Foo: def __eq__(self, other): return NotImplemented class Bar: pass print(Foo() == Bar()) # False (prueba mediante Bar.__eq__)
Un desarrollador creó una clase User y solo implementó eq sin hash, para que los usuarios se consideraran iguales por correo electrónico. Luego intentó usar objetos User en un set/comos claves de diccionario.
Pros:
Contras:
Un programador utiliza @total_ordering e implementa eq y lt con verificación de tipo, y también añade hash si la clase es inmutable. Los objetos se comparan, ordenan correctamente, y funcionan como claves de diccionario.
Pros:
Contras: