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 decmp(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.







