cmp_to_key: adapta comparadores antiguos a sorted

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

Python moderno prefiere funciones key para ordenar porque la clave se calcula una sola vez por elemento y después se compara de manera eficiente. Sin embargo, sistemas heredados, bibliotecas externas y reglas lingüísticas todavía pueden proporcionar un comparador de dos argumentos que devuelve un número negativo, cero o positivo. functools.cmp_to_key() adapta ese comparador a la interfaz aceptada por sorted(), list.sort() y otras operaciones.

Esta guía explica cómo funciona el adaptador, cómo migrar comparadores antiguos, conservar la estabilidad, ordenar con locale, crear desempates deterministas, evitar relaciones incoherentes y decidir cuándo reescribir la lógica como una función key.

Comparador de tres vías

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

Solo importa el signo. No es obligatorio devolver exactamente -1 o 1. Cualquier valor negativo indica que a debe aparecer antes que b, cero indica equivalencia para esa ordenación y un valor positivo indica que debe aparecer después.

Usar 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() crea una clase wrapper. Cada instancia guarda un valor original e implementa comparaciones ricas llamando al comparador suministrado.

Por qué Python prefiere key

sorted(personas, key=lambda persona: persona.nombre.casefold())

Una función key se ejecuta una vez por elemento. Un comparador puede ejecutarse muchas veces durante el algoritmo. Si la normalización es costosa, la diferencia de rendimiento puede ser considerable.

Migrar código heredado

def comparar_productos(a, b):
    if a.precio != b.precio:
        return -1 if a.precio < b.precio else 1
    return -1 if a.nombre < b.nombre else (1 if a.nombre > b.nombre else 0)

productos.sort(key=cmp_to_key(comparar_productos))

El adaptador conserva la regla mientras el sistema se moderniza. La misma relación suele expresarse mejor como:

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

La tupla es más corta, normalmente más rápida y más fácil de probar.

Orden descendente

Para invertir toda la ordenación, usa reverse=True. Para direcciones mixtas, transforma solo el componente correspondiente de la clave. Evita invertir manualmente todos los signos del comparador sin necesidad.

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

Desempates deterministas

El comparador debe devolver cero solo cuando los elementos sean equivalentes para esa relación. Añade desempates cuando el resultado deba ser estable entre ejecuciones:

def comparar_tareas(a, b):
    if a.prioridad != b.prioridad:
        return b.prioridad - a.prioridad
    if a.creada_en != b.creada_en:
        return -1 if a.creada_en < b.creada_en else 1
    return (a.id > b.id) - (a.id < b.id)

La última expresión convierte dos comparaciones booleanas en -1, 0 o 1 sin restar identificadores potencialmente grandes.

Estabilidad

La ordenación de Python es estable: los elementos considerados equivalentes conservan su posición relativa original. Si el comparador devuelve cero para dos registros, esa propiedad puede aprovecharse en ordenaciones por etapas.

registros.sort(key=lambda registro: registro.nombre)
registros.sort(key=lambda registro: registro.departamento)

Después de la segunda operación, los nombres permanecen ordenados dentro de cada departamento.

Locale y strcoll

Un caso clásico es adaptar locale.strcoll:

import locale
from functools import cmp_to_key

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

strcoll compara dos strings según el locale activo. Para mejorar rendimiento, locale.strxfrm suele ser preferible como key:

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

El locale es estado global del proceso y puede ser problemático en servidores concurrentes. Define una política clara o usa una biblioteca de collation con configuración aislada.

Comparadores no transitivos

La relación debe ser transitiva. Si A aparece antes que B y B antes que C, A debe aparecer antes que C. Una regla cíclica no es una ordenación válida:

# piedra < papel, papel < tijera, tijera < piedra

Piedra, papel o tijera representa un juego, no un orden total adecuado para sorted().

Antisimetría del signo

El signo de cmp(a, b) debe ser el opuesto del signo de cmp(b, a). Si ambas llamadas devuelven un número negativo, el algoritmo recibe información contradictoria.

Consistencia con igualdad

Dos elementos pueden ser equivalentes para ordenar sin ser iguales como objetos, pero debe ser intencional. Un comparador que ignora mayúsculas puede considerar equivalentes “Ana” y “ana”. La estabilidad conservará su orden de entrada.

No devuelvas bool

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

Los booleanos son enteros 0 y 1. Esa función nunca devuelve un número negativo y viola el contrato. Una forma compacta correcta es:

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

None y valores ausentes

Define de forma explícita dónde deben aparecer los valores ausentes:

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)

Una key equivalente suele ser lambda valor: (valor is None, valor).

Tipos heterogéneos

Python 3 no impone un orden automático entre números, strings y objetos arbitrarios. Un comparador puede definir categorías, pero la política debe estar documentada y ser determinista.

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

Una clave compuesta como (categoria(valor), valor_normalizado) suele ser más segura.

Versiones textuales

El orden lexicográfico coloca “10” antes que “2”. Un comparador puede dividir componentes, pero una key es más eficiente:

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

El comparador debe ser puro

No modifiques objetos, no cambies configuración global ni dependas del número de llamadas. El algoritmo puede comparar el mismo par varias veces y en direcciones distintas. Los efectos secundarios hacen que el resultado dependa de detalles internos.

Excepciones durante la ordenación

Si el comparador lanza una excepción, la ordenación se interrumpe. Con list.sort(), la lista puede haber quedado parcialmente reorganizada. Valida entradas antes y mantén el comparador pequeño y predecible.

Rendimiento

Ordenar n elementos requiere aproximadamente O(n log n) comparaciones. cmp_to_key añade wrappers y llamadas Python. Una función key calcula O(n) transformaciones y después compara valores frecuentemente optimizados en C.

Cache de transformaciones

Si no puedes cambiar la API, puedes cachear normalizaciones caras por valor inmutable o identidad. Controla memoria y mutabilidad. En general, decorar-ordenar-extraer es más simple: calcula claves una vez, ordena pares y recupera los objetos.

Combinar con reverse

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

reverse=True invierte el resultado final conservando estabilidad. Confirma si deseas invertir toda la relación o solo uno de sus criterios.

Pruebas recomendadas

  • cmp(a, a) == 0.
  • Los signos de cmp(a, b) y cmp(b, a) son opuestos.
  • Transitividad para tríos de valores.
  • Los elementos equivalentes conservan el orden original.
  • Casos con None, NaN, strings vacías y números extremos.
  • El resultado coincide con una función key de referencia cuando existe.

Errores comunes

  • Devolver bool: el contrato exige negativo, cero o positivo.
  • Añadir efectos secundarios: el resultado se vuelve impredecible.
  • Ignorar transitividad: la relación no es ordenable.
  • Usar cmp_to_key para código nuevo ordinario: una key suele ser mejor.
  • Cambiar locale en un servidor concurrente: es estado global.
  • Repetir una normalización cara: precalcula la clave.

Ejemplo completo: orden natural de archivos

import re
from functools import cmp_to_key

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

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

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

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

Como partes() se calcula muchas veces, la implementación recomendada es directamente sorted(archivos, key=partes). El ejemplo muestra adaptación, no la primera opción para código nuevo.

Cuándo usar cmp_to_key

Úsalo al integrar un comparador obligatorio de código heredado, un protocolo externo o una API como strcoll. Para código nuevo bajo tu control, prefiere funciones key, tuplas y reverse=True.

Conclusión

functools.cmp_to_key() conecta comparadores de dos argumentos con el modelo moderno de ordenación por claves. Conserva reglas existentes, pero no corrige relaciones incoherentes y normalmente cuesta más que calcular una clave por elemento.

La documentación oficial de cmp_to_key describe el adaptador. Úsalo para compatibilidad, prueba las propiedades de la ordenación y migra a funciones key siempre que sea posible.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Close-up of a laptop screen with code and a coffee mug, perfect for tech abstract themes.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    total_ordering: genera comparaciones consistentes

    Aprende total_ordering en Python para generar comparaciones coherentes, devolver NotImplemented, integrar dataclasses y probar órdenes.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    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