total_ordering: gere comparações consistentes

Publicado em: 29/08/2026
Tempo de leitura: 6 minutos
A laptop screen showing a code editor with visible programming code in a dimly lit environment.

Classes de domínio frequentemente precisam ser ordenadas: versões, prioridades, datas especiais, faixas, produtos ou registros. Implementar manualmente __lt__, __le__, __gt__ e __ge__ gera repetição e aumenta o risco de relações inconsistentes. O decorator functools.total_ordering completa os métodos de ordenação quando a classe fornece __eq__ e pelo menos uma comparação de ordem.

Neste guia, você aprenderá a criar ordens totais corretas, retornar NotImplemented, comparar chaves compostas, integrar com dataclasses, entender herança e desempenho, testar propriedades matemáticas e decidir quando escrever todos os métodos explicitamente.

O problema da repetição

class Versao:
    def __init__(self, maior: int, menor: int):
        self.maior = maior
        self.menor = menor

Para permitir <, <=, > e >=, seria possível implementar quatro métodos semelhantes. Qualquer diferença entre eles pode produzir resultados contraditórios.

Primeiro uso de total_ordering

from functools import total_ordering

@total_ordering
class Versao:
    def __init__(self, maior: int, menor: int):
        self.maior = maior
        self.menor = menor

    def __eq__(self, outro: object) -> bool:
        if not isinstance(outro, Versao):
            return NotImplemented
        return (self.maior, self.menor) == (outro.maior, outro.menor)

    def __lt__(self, outro: object) -> bool:
        if not isinstance(outro, Versao):
            return NotImplemented
        return (self.maior, self.menor) < (outro.maior, outro.menor)

Com igualdade e menor-que definidos, o decorator adiciona os demais operadores ausentes. A tupla concentra a chave de comparação e aproveita a ordem lexicográfica do Python.

O que o decorator gera

total_ordering procura um dos métodos __lt__, __le__, __gt__ ou __ge__. A partir dele e de __eq__, sintetiza os outros. Métodos já definidos na classe ou herdados não são substituídos.

Por que retornar NotImplemented

def __lt__(self, outro: object) -> bool:
    if not isinstance(outro, Versao):
        return NotImplemented
    return self.chave < outro.chave

NotImplemented não é o mesmo que False. Ele informa ao interpretador que aquela combinação de tipos não é suportada e permite tentar a operação refletida do outro objeto. Se nenhum lado souber comparar, Python gera TypeError.

Não use raise NotImplemented

NotImplemented é um valor especial. NotImplementedError é uma exceção usada para métodos ainda não implementados. Em operadores de comparação, normalmente você deve retornar o valor, não lançar a exceção.

Centralizando a chave

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

Uma propriedade ou método privado reduz duplicação entre __eq__ e __lt__. A chave deve conter exatamente os campos que definem identidade de ordenação. Se igualdade usa campos diferentes da ordenação, as relações podem ficar incoerentes.

Ordem total e ordem parcial

Uma ordem total exige que elementos comparáveis tenham uma relação consistente: um é menor, igual ou maior. Alguns domínios não possuem ordem total natural. Conjuntos, dependências e permissões podem formar apenas uma ordem parcial. Forçar total_ordering nesses casos cria uma regra artificial e possivelmente enganosa.

Comparando tipos diferentes

v = Versao(3, 12)
# v < 10 deve falhar com TypeError, não fingir False

Retornar False para qualquer tipo incompatível faria parecer que os objetos são comparáveis. Retorne NotImplemented e deixe Python aplicar o protocolo correto.

Subclasses

isinstance(outro, Versao) aceita subclasses. Isso pode ser desejado quando todas compartilham a mesma semântica. Se uma subclasse adiciona campos que alteram identidade, comparar instâncias pode violar simetria. Em domínios estritos, use type(outro) is type(self).

Igualdade e hash

Ao definir __eq__, Python pode tornar a classe não hashable, pois igualdade personalizada exige um hash compatível. Se objetos forem imutáveis e usados como chaves, implemente __hash__ com os mesmos campos ou use dataclass frozen.

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

Nunca calcule hash com campos mutáveis que podem mudar depois da inserção em um conjunto ou dicionário.

Integração com dataclass

from dataclasses import dataclass

@dataclass(order=True, frozen=True)
class Versao:
    maior: int
    menor: int

Quando a ordem segue a sequência de campos, @dataclass(order=True) costuma ser mais simples e gera igualdade e comparações. O guia de dataclasses no Python mostra opções como compare=False.

Quando total_ordering é melhor que dataclass

Use total_ordering quando a classe não é uma dataclass, quando a chave exige transformação, quando campos entram em ordens diferentes ou quando a comparação precisa validar compatibilidade.

Exemplo com prioridade

@total_ordering
class Tarefa:
    def __init__(self, prioridade: int, criada_em: float, titulo: str):
        self.prioridade = prioridade
        self.criada_em = criada_em
        self.titulo = titulo

    @property
    def chave(self):
        return (-self.prioridade, self.criada_em, self.titulo)

    def __eq__(self, outro):
        if not isinstance(outro, Tarefa):
            return NotImplemented
        return self.chave == outro.chave

    def __lt__(self, outro):
        if not isinstance(outro, Tarefa):
            return NotImplemented
        return self.chave < outro.chave

Negar a prioridade coloca valores maiores primeiro quando a lista é ordenada de forma crescente. A data e o título funcionam como desempates determinísticos.

Valores especiais

Float com NaN não obedece às expectativas comuns de ordem total: comparações podem ser falsas inclusive consigo mesmo. Se o domínio admite NaN, normalize, rejeite ou defina uma política explícita.

None e sentinelas

Python 3 não ordena automaticamente None e números. Converta valores ausentes para uma chave comparável:

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

A posição de ausentes depende do primeiro componente. Documente se ficam no início ou no fim.

Strings e locale

Comparações de strings usam pontos de código, não regras linguísticas completas. Para ordenação humana, normalize caixa, acentos ou use uma chave de locale apropriada. A classe deve manter igualdade e ordem consistentes com essa normalização.

Desempenho

Os métodos gerados podem adicionar chamadas extras e stack traces mais complexos. Na maioria dos domínios, a diferença é irrelevante. Em loops extremamente quentes com milhões de comparações, implementar diretamente os seis métodos pode ser mais rápido e mais fácil de perfilar.

Herança e métodos existentes

O decorator não sobrescreve métodos já presentes ou herdados. Uma classe base pode introduzir uma comparação incompatível com o método definido na subclasse. Inspecione a hierarquia e prefira uma única fonte para a semântica.

Ordenação de coleções

versoes = [Versao(3, 12), Versao(3, 10), Versao(4, 0)]
print(sorted(versoes))

sorted() depende principalmente de __lt__. total_ordering é mais útil quando consumidores também usam <=, > e >=. Se só existe uma necessidade local de ordenação, uma função key= pode ser mais simples.

Preferindo key functions

sorted(produtos, key=lambda p: (p.preco, p.nome))

Não implemente ordem global em uma classe quando diferentes telas precisam ordenar por preço, nome, avaliação ou data. Métodos ricos devem representar uma ordem natural e estável do domínio.

Testando propriedades

Além de exemplos específicos, teste propriedades:

  • Reflexividade da igualdade: a == a.
  • Simetria: se a == b, então b == a.
  • Transitividade: se a < b e b < c, então a < c.
  • Coerência: a <= b equivale a a < b or a == b.
  • Incompatíveis retornam NotImplemented e resultam em TypeError.

Testes baseados em propriedades ajudam a encontrar casos de desempate e valores extremos.

Erros comuns

  • Retornar False para tipo incompatível: devolva NotImplemented.
  • Usar campos diferentes em eq e lt: a ordem pode ficar incoerente.
  • Forçar ordem total onde não existe: prefira relações explícitas.
  • Ignorar NaN: floats especiais quebram propriedades.
  • Implementar ordem global para todas as telas: use key functions.
  • Esquecer hash: igualdade personalizada afeta uso em dict e set.

Exemplo completo: faixa de versão

from functools import total_ordering

@total_ordering
class Versao:
    __slots__ = ("maior", "menor", "patch")

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

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

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

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

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

    def __repr__(self) -> str:
        return f"Versao{self.chave}"

A classe tem uma chave única para igualdade, ordem e hash. Tipos diferentes não são comparados silenciosamente.

Conclusão

functools.total_ordering reduz boilerplate e concentra a semântica de comparação em igualdade e um operador fundamental. Ele funciona melhor quando o domínio possui uma ordem total natural e a classe retorna NotImplemented para tipos incompatíveis.

A documentação oficial de total_ordering detalha o decorator. Prefira chaves consistentes, teste propriedades e implemente todos os operadores manualmente apenas quando desempenho ou clareza justificarem.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    inspect.signature: leia parâmetros de funções

    Aprenda inspect.signature no Python para ler parâmetros, vincular argumentos, preservar decorators e gerar interfaces dinâmicas.

    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 Avançado
    Foto de perfil de Leandro Hirt da Academify

    get_origin e get_args: inspecione tipos genéricos

    Aprenda get_origin e get_args no Python para inspecionar genéricos, uniões, Annotated, Literal e aliases com segurança.

    Ler mais

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

    LiteralString no Python: strings confiáveis

    Aprenda LiteralString no Python para restringir SQL, templates e comandos a strings confiáveis e reduzir injeções com análise estática.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    dataclass_transform no Python: classes geradas

    Aprenda dataclass_transform no Python para tipar decorators, metaclasses e frameworks que geram __init__, campos e métodos.

    Ler mais

    Tempo de leitura: 6 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 Avançado
    Foto de perfil de Leandro Hirt da Academify

    TypeVarTuple no Python: genéricos variádicos

    Aprenda TypeVarTuple no Python para preservar tuplas heterogêneas, modelar dimensões e criar genéricos com vários parâmetros.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Close-up of a laptop screen with code and a coffee mug, perfect for tech abstract themes.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    assert_type e reveal_type: teste inferência de tipos

    Aprenda assert_type e reveal_type no Python para inspecionar inferência, testar APIs tipadas e evitar regressões no analisador.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026