cmp_to_key: adapte comparadores antigos ao sorted

Publicado em: 29/08/2026
Tempo de leitura: 5 minutos
Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.

Python moderno prefere funções key para ordenação, porque uma chave é calculada uma vez por elemento e comparada de forma eficiente. Entretanto, sistemas legados, bibliotecas externas e regras linguísticas às vezes fornecem um comparador de dois argumentos que retorna valor negativo, zero ou positivo. functools.cmp_to_key() adapta esse comparador para a interface aceita por sorted(), list.sort(), min(), max() e outras operações.

Neste guia, você aprenderá como o adaptador funciona, como migrar comparadores antigos, preservar estabilidade, ordenar com locale, criar desempates, evitar comparadores inconsistentes e decidir quando reescrever a lógica como key function.

Comparador de três vias

def comparar(a, b):
    if a < b:
        return -1
    if a > b:
        return 1
    return 0

Esse estilo era comum em APIs antigas. O sinal importa; não é obrigatório retornar exatamente -1 ou 1. Qualquer valor negativo indica “a antes de b”, zero indica equivalência e positivo indica “a depois de b”.

Usando cmp_to_key

from functools import cmp_to_key

valores = [10, 2, 30, 4]
ordenados = sorted(valores, key=cmp_to_key(comparar))

cmp_to_key() cria uma classe wrapper cujas instâncias guardam cada valor e implementam comparações ricas chamando o comparador original.

Por que Python prefere key

sorted(pessoas, key=lambda pessoa: pessoa.nome.casefold())

Uma key function é executada uma vez por item. Um comparador pode ser executado muitas vezes durante o algoritmo de ordenação. Se a transformação é cara, a diferença de desempenho pode ser grande.

Migrando código legado

def comparar_produtos(a, b):
    if a.preco != b.preco:
        return -1 if a.preco < b.preco else 1
    return -1 if a.nome < b.nome else (1 if a.nome > b.nome else 0)

produtos.sort(key=cmp_to_key(comparar_produtos))

O adaptador permite manter a regra enquanto o sistema é modernizado. Depois, a mesma lógica pode ser expressa como:

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

A versão com tupla é menor, mais rápida e mais fácil de testar.

Ordem descendente

Para tipos numéricos, normalmente use reverse=True ou negue a parte apropriada da chave. Não inverta todos os sinais no comparador sem necessidade.

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

Desempates

Um comparador deve retornar zero somente quando os elementos são equivalentes para aquela ordem. Se você quer resultado determinístico, adicione desempates:

def comparar_tarefas(a, b):
    if a.prioridade != b.prioridade:
        return b.prioridade - a.prioridade
    if a.criada_em != b.criada_em:
        return -1 if a.criada_em < b.criada_em else 1
    return (a.id > b.id) - (a.id < b.id)

O último padrão converte comparações booleanas em -1, 0 ou 1 sem subtrair valores potencialmente grandes.

Estabilidade

A ordenação do Python é estável: elementos considerados equivalentes mantêm sua ordem relativa original. Se o comparador retorna zero para dois itens, essa estabilidade pode ser usada para ordenar por múltiplos critérios em etapas.

dados.sort(key=lambda x: x.nome)
dados.sort(key=lambda x: x.departamento)

Na segunda ordenação, nomes permanecem ordenados dentro de cada departamento.

Locale e strcoll

Um caso clássico é adaptar locale.strcoll:

import locale
from functools import cmp_to_key

locale.setlocale(locale.LC_COLLATE, "")
nomes = sorted(nomes, key=cmp_to_key(locale.strcoll))

strcoll compara duas strings segundo o locale atual. Para melhor desempenho, locale.strxfrm costuma ser preferível como key:

nomes = sorted(nomes, key=locale.strxfrm)

Locale é estado global do processo e pode ser problemático em servidores concorrentes. Defina políticas claras ou use bibliotecas de collation isoladas.

Comparadores não transitivos

Um comparador deve ser transitivo. Se A vem antes de B e B antes de C, A deve vir antes de C. Regras cíclicas produzem resultados imprevisíveis:

# pedra < papel, papel < tesoura, tesoura < pedra

Esse domínio não possui uma ordem total adequada para sorted.

Antissimetria

O sinal de cmp(a, b) deve ser o oposto de cmp(b, a). Se ambos retornam negativo, o algoritmo recebe informações contraditórias.

Consistência com igualdade

É possível ordenar itens em grupos equivalentes sem que a == b seja verdadeiro, mas isso precisa ser intencional. Por exemplo, comparação case-insensitive pode considerar “Ana” e “ana” equivalentes. A estabilidade preservará a ordem original.

Não retorne boolean

def errado(a, b):
    return a < b

Booleanos são inteiros 0 e 1. Esse comparador nunca retorna negativo e quebra o contrato. Use a expressão:

def comparar(a, b):
    return (a > b) - (a < b)

None e valores ausentes

Defina explicitamente onde valores ausentes aparecem:

def comparar_none(a, b):
    if a is None and b is None:
        return 0
    if a is None:
        return 1
    if b is None:
        return -1
    return (a > b) - (a < b)

Uma key function equivalente pode ser key=lambda x: (x is None, x).

Tipos heterogêneos

Python 3 não impõe uma ordem automática entre números, strings e objetos arbitrários. Um comparador pode definir uma política por categoria, mas isso deve estar documentado.

def categoria(valor):
    if isinstance(valor, (int, float)):
        return 0
    if isinstance(valor, str):
        return 1
    return 2

Frequentemente uma chave composta (categoria(valor), representação) é mais segura.

Comparar versões textuais

Ordenação lexicográfica coloca “10” antes de “2”. Um comparador pode dividir componentes, mas uma key function que converte para tupla é melhor:

def chave_versao(texto: str):
    return tuple(int(parte) for parte in texto.split("."))

Objetos mutáveis

Não altere objetos dentro do comparador. O algoritmo pode comparar o mesmo elemento várias vezes e em ordens diferentes. Efeitos colaterais tornam o resultado dependente do caminho interno da ordenação.

Exceções

Se o comparador lançar exceção, a ordenação é interrompida e a lista pode ficar parcialmente reorganizada quando list.sort() é usado. Valide entradas antes de ordenar e mantenha o comparador puro.

Desempenho

Com n elementos, uma ordenação realiza aproximadamente O(n log n) comparações. cmp_to_key adiciona wrappers e chamadas Python. Uma key function calcula O(n) chaves e depois compara valores geralmente otimizados em C.

Cache dentro do comparador

Se não puder reescrever a API, você pode cachear transformações caras por identidade ou valor, mas deve controlar memória e mutabilidade. Em geral, decorar-ordenar-remover é mais simples: calcule chaves uma vez, ordene pares e extraia objetos.

Combinação com reverse

sorted(itens, key=cmp_to_key(comparar), reverse=True)

reverse=True inverte o resultado final preservando estabilidade. Confirme se a semântica desejada é inverter toda a ordem ou apenas um critério.

Testes recomendados

  • cmp(a, a) == 0.
  • O sinal de cmp(a, b) é o oposto de cmp(b, a).
  • Transititividade para trios de valores.
  • Entradas duplicadas preservam estabilidade.
  • Valores extremos, None, NaN e strings vazias.
  • Resultado coincide com uma key function de referência quando existir.

Erros comuns

  • Retornar bool: o contrato exige negativo, zero ou positivo.
  • Comparador com efeitos colaterais: resultados ficam imprevisíveis.
  • Ignorar transitividade: a ordem não é consistente.
  • Usar cmp_to_key por conveniência: key functions normalmente são melhores.
  • Alterar locale em servidor concorrente: locale é estado global.
  • Executar transformação cara a cada comparação: pré-calcule a chave.

Exemplo completo: nomes e números naturais

import re
from functools import cmp_to_key

_padrao = re.compile(r"(\d+)")

def partes(texto: str):
    return [
        int(parte) if parte.isdigit() else parte.casefold()
        for parte in _padrao.split(texto)
    ]

def comparar_natural(a: str, b: str) -> int:
    pa = partes(a)
    pb = partes(b)
    return (pa > pb) - (pa < pb)

arquivos = ["item10.txt", "item2.txt", "item1.txt"]
print(sorted(arquivos, key=cmp_to_key(comparar_natural)))

Como partes() é calculada repetidamente, a versão recomendada é diretamente sorted(arquivos, key=partes). O exemplo mostra como cmp_to_key adapta uma API existente, não como primeira escolha.

Quando usar cmp_to_key

Use-o ao integrar um comparador obrigatório fornecido por legado, protocolo externo ou API como strcoll. Para código novo sob seu controle, prefira key functions, tuplas e reverse=True.

Conclusão

functools.cmp_to_key() é uma ponte entre comparadores de dois argumentos e o modelo moderno de chaves do Python. Ele preserva regras existentes, mas não corrige comparadores inconsistentes e normalmente custa mais que calcular uma chave uma vez.

A documentação oficial de cmp_to_key descreve o adaptador. Use-o para compatibilidade, teste propriedades da ordem e migre para key functions sempre que possível.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    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

    total_ordering: gere comparações consistentes

    Aprenda total_ordering no Python para gerar comparações consistentes, usar NotImplemented, integrar dataclasses e testar ordens.

    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

    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