total_ordering: genera comparaciones consistentes

Publicado el: 29/08/2026
Tempo de leitura: 5 minutos
Close-up of a laptop screen with code and a coffee mug, perfect for tech abstract themes.

Las clases de dominio necesitan a menudo un orden natural: versiones, prioridades, rangos, productos, fechas o registros. Implementar manualmente __lt__, __le__, __gt__ y __ge__ crea repetición y aumenta el riesgo de resultados contradictorios. El decorator functools.total_ordering completa los métodos ausentes cuando una clase define __eq__ y al menos una comparación de orden.

Esta guía explica cómo diseñar un orden total correcto, devolver NotImplemented, comparar claves compuestas, integrar dataclasses, manejar herencia y valores especiales, medir rendimiento y probar propiedades matemáticas.

El problema de la repetición

class Version:
    def __init__(self, mayor: int, menor: int):
        self.mayor = mayor
        self.menor = menor

Para soportar todos los operadores habría que escribir varios métodos casi iguales. Una pequeña diferencia puede hacer que a < b contradiga a >= b.

Primer uso de total_ordering

from functools import total_ordering

@total_ordering
class Version:
    def __init__(self, mayor: int, menor: int):
        self.mayor = mayor
        self.menor = menor

    def __eq__(self, otro: object) -> bool:
        if not isinstance(otro, Version):
            return NotImplemented
        return (self.mayor, self.menor) == (otro.mayor, otro.menor)

    def __lt__(self, otro: object) -> bool:
        if not isinstance(otro, Version):
            return NotImplemented
        return (self.mayor, self.menor) < (otro.mayor, otro.menor)

Con igualdad y menor-que definidos, el decorator añade los operadores restantes. La tupla centraliza la clave y aprovecha el orden lexicográfico de Python.

Qué genera el decorator

total_ordering busca uno de __lt__, __le__, __gt__ o __ge__. Junto con __eq__, ese método permite sintetizar los demás. Los métodos ya definidos o heredados no se reemplazan.

Por qué importa NotImplemented

def __lt__(self, otro: object) -> bool:
    if not isinstance(otro, Version):
        return NotImplemented
    return self.clave < otro.clave

NotImplemented no significa False. Indica que esa combinación de operandos no está soportada y permite a Python probar la operación reflejada del otro objeto. Si ninguno sabe comparar, se genera TypeError.

No lances NotImplemented

NotImplemented es un valor especial. NotImplementedError es una excepción para métodos todavía no implementados. En comparaciones ricas normalmente se devuelve el valor.

Centralizar la clave

@property
def clave(self) -> tuple[int, int]:
    return self.mayor, self.menor

Una propiedad o método privado evita duplicación entre __eq__ y __lt__. La clave debe contener exactamente los campos que definen identidad de orden. Si igualdad y orden utilizan campos distintos, la relación puede ser incoherente.

Orden total y orden parcial

Un orden total espera que los elementos comparables sean menores, iguales o mayores de forma consistente. Algunos dominios solo tienen orden parcial. Conjuntos, dependencias, capacidades y permisos pueden contener valores incomparables. Forzar un orden total crea una regla artificial.

Comparar tipos diferentes

version = Version(3, 12)
# version < 10 debe producir TypeError, no False silencioso

Devolver False para cualquier objeto incompatible sugiere falsamente que todos participan en el mismo orden. Devuelve NotImplemented.

Subclases

isinstance(otro, Version) acepta subclases. Es útil cuando comparten semántica. Si una subclase añade campos que cambian identidad, la comparación cruzada puede romper simetría. Los dominios estrictos pueden exigir type(otro) is type(self).

Igualdad y hash

Definir __eq__ puede hacer que la clase deje de ser hashable, porque la igualdad personalizada requiere un hash compatible. Los objetos inmutables usados como claves pueden implementar:

def __hash__(self) -> int:
    return hash(self.clave)

No uses campos mutables que cambien después de insertar el objeto en un set o diccionario.

Integración con dataclass

from dataclasses import dataclass

@dataclass(order=True, frozen=True)
class Version:
    mayor: int
    menor: int

Cuando el orden sigue la declaración de campos, @dataclass(order=True) suele ser más simple y genera igualdad y comparaciones. La guía de dataclasses en Python explica compare=False y objetos frozen.

Cuándo total_ordering es mejor

Úsalo cuando la clase no es dataclass, la clave requiere normalización, los campos siguen otro orden o se necesita comprobar compatibilidad de operandos.

Ejemplo con prioridad

@total_ordering
class Tarea:
    def __init__(self, prioridad: int, creada: float, titulo: str):
        self.prioridad = prioridad
        self.creada = creada
        self.titulo = titulo

    @property
    def clave(self):
        return (-self.prioridad, self.creada, self.titulo)

    def __eq__(self, otro):
        if not isinstance(otro, Tarea):
            return NotImplemented
        return self.clave == otro.clave

    def __lt__(self, otro):
        if not isinstance(otro, Tarea):
            return NotImplemented
        return self.clave < otro.clave

Negar la prioridad coloca valores mayores primero en un orden ascendente. La fecha y el título crean desempates deterministas.

Valores especiales

NaN no se comporta como un valor ordinario en un orden total. Algunas comparaciones son falsas incluso contra sí mismo. Si el dominio admite NaN, recházalo, normalízalo o define una política explícita.

None y sentinelas

Python 3 no ordena automáticamente None y números. Convierte ausencias en una clave comparable:

clave = (valor is None, valor if valor is not None else 0)

El primer componente controla si los ausentes aparecen al inicio o al final.

Strings y locale

Las strings se comparan por puntos de código Unicode, no por reglas lingüísticas completas. Para orden humano, normaliza mayúsculas, acentos o usa una clave de locale. Igualdad y orden deben compartir la misma normalización.

Rendimiento

Los métodos generados pueden añadir llamadas y stack traces más profundos. En la mayoría de aplicaciones la diferencia es insignificante. En loops con millones de comparaciones, métodos explícitos pueden ser más rápidos y fáciles de perfilar.

Herencia y métodos existentes

El decorator no reemplaza métodos presentes o heredados. Una clase base puede aportar una comparación incompatible con la subclase. Mantén la semántica en una capa clara.

Ordenar colecciones

versiones = [Version(3, 12), Version(3, 10), Version(4, 0)]
print(sorted(versiones))

sorted() depende principalmente de __lt__. total_ordering es más útil cuando los consumidores también usan <=, > y >=. Para una necesidad local, una función key= puede ser más simple.

Preferir key functions para varias vistas

sorted(productos, key=lambda producto: (producto.precio, producto.nombre))

No impongas un orden global si distintas pantallas ordenan por precio, nombre, valoración o fecha. La comparación rica debe representar un orden natural estable.

Probar propiedades

  • Reflexividad de igualdad: a == a.
  • Simetría: si a == b, entonces b == a.
  • Transitividad: si a < b y b < c, entonces a < c.
  • Coherencia: a <= b coincide con a < b or a == b.
  • Operandos incompatibles terminan en TypeError.

Las pruebas basadas en propiedades encuentran casos extremos y errores de desempate.

Errores comunes

  • Devolver False para tipos incompatibles: devuelve NotImplemented.
  • Usar campos diferentes en eq y lt: orden e igualdad pueden contradecirse.
  • Forzar orden total en un dominio parcial: expón relaciones explícitas.
  • Ignorar NaN: rompe expectativas habituales.
  • Definir un orden global para toda vista: usa key functions.
  • Olvidar hash: igualdad personalizada afecta dict y set.

Ejemplo completo: versión semántica

from functools import total_ordering

@total_ordering
class Version:
    __slots__ = ("mayor", "menor", "patch")

    def __init__(self, mayor: int, menor: int, patch: int = 0):
        self.mayor = mayor
        self.menor = menor
        self.patch = patch

    @property
    def clave(self) -> tuple[int, int, int]:
        return self.mayor, self.menor, self.patch

    def __eq__(self, otro: object) -> bool:
        if type(otro) is not type(self):
            return NotImplemented
        return self.clave == otro.clave

    def __lt__(self, otro: object) -> bool:
        if type(otro) is not type(self):
            return NotImplemented
        return self.clave < otro.clave

    def __hash__(self) -> int:
        return hash(self.clave)

    def __repr__(self) -> str:
        return f"Version{self.clave}"

La clase usa una única clave para igualdad, orden y hash. Los tipos distintos no se comparan silenciosamente.

Conclusión

functools.total_ordering reduce boilerplate y concentra la semántica en igualdad y un operador fundamental. Funciona mejor cuando el dominio posee un orden total real y los operandos incompatibles devuelven NotImplemented.

La documentación oficial de total_ordering describe los métodos generados. Usa claves consistentes, prueba propiedades e implementa todos los operadores manualmente solo cuando rendimiento o depuración lo justifiquen.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    inspect.signature: inspecciona parámetros de funciones

    Aprende inspect.signature en Python para leer parámetros, vincular argumentos, conservar decorators y generar interfaces dinámicas.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    get_origin y get_args: inspecciona tipos genéricos

    Aprende get_origin y get_args en Python para inspeccionar genéricos, uniones, Annotated, Literal, alias y metadatos de runtime.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Striking image of a red-bellied python showcasing its vibrant scales in dramatic lighting.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    LiteralString en Python: cadenas confiables

    Aprende LiteralString en Python para restringir SQL, templates y comandos a cadenas confiables y reducir riesgos de inyección.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    dataclass_transform en Python: clases generadas

    Aprende dataclass_transform en Python para tipar decorators, clases base y metaclases que generan campos, __init__ y métodos.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    TypeVarTuple en Python: genéricos variádicos

    Aprende TypeVarTuple en Python para conservar tuplas heterogéneas, modelar dimensiones y crear genéricos con parámetros variables.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    assert_type y reveal_type: prueba inferencia de tipos

    Aprende assert_type y reveal_type en Python para inspeccionar inferencia, probar APIs tipadas y evitar regresiones estáticas.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026