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)ycmp(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.







