Comparar dos versiones de un texto es útil en revisión, auditoría, migraciones, pruebas, sincronización y herramientas de línea de comandos. En lugar de descubrir solamente que los contenidos son distintos, muchas aplicaciones necesitan mostrar qué líneas fueron eliminadas, añadidas o modificadas. El módulo difflib en Python ofrece comparación de secuencias, deltas legibles, sugerencias por similitud e informes HTML sin dependencias externas.
Esta guía explica SequenceMatcher, get_close_matches(), unified_diff(), context_diff(), ndiff(), restore() y HtmlDiff. Complementa nuestros artículos sobre listas en Python, slicing, archivos temporales, collections y archivos de texto.
Qué compara difflib
difflib trabaja con secuencias cuyos elementos son hashable. Esto incluye cadenas, listas de líneas y listas de tokens.
from difflib import SequenceMatcher
anterior = "Python es simple"
nuevo = "Python es muy simple"
matcher = SequenceMatcher(None, anterior, nuevo)
print(matcher.ratio())El algoritmo encuentra bloques contiguos comunes y aplica la misma idea recursivamente a las partes restantes. Favorece coincidencias naturales para personas, pero no promete una secuencia mínima de ediciones.
Similitud con ratio()
ratio() devuelve un valor entre cero y uno. Los valores próximos a uno indican gran similitud.
similitud = SequenceMatcher(None, "configuracion", "configuración").ratio()
print(round(similitud, 3))El valor no es una probabilidad y no tiene un umbral universal. Calibra el corte con ejemplos reales y mide falsos positivos y negativos.
El orden de argumentos puede influir
La documentación oficial de difflib advierte que ratio() puede cambiar al invertir las secuencias.
print(SequenceMatcher(None, "tide", "diet").ratio())
print(SequenceMatcher(None, "diet", "tide").ratio())Si tu regla necesita simetría, calcula ambas direcciones o utiliza una métrica diseñada para ser simétrica.
quick_ratio() y real_quick_ratio()
Estos métodos devuelven límites superiores más rápidos para la razón completa.
matcher = SequenceMatcher(None, "abcd", "bcde")
print(matcher.real_quick_ratio())
print(matcher.quick_ratio())
print(matcher.ratio())En una búsqueda grande, usa las estimaciones para descartar candidatos obvios y calcula la razón completa solo para los restantes.
Encontrar bloques coincidentes
get_matching_blocks() informa posiciones y tamaños de los bloques iguales.
matcher = SequenceMatcher(None, "abxcd", "abcd")
for bloque in matcher.get_matching_blocks():
print(bloque)El último bloque siempre es un centinela de tamaño cero. Esta API sirve para resaltado y visualizaciones personalizadas.
Transformaciones con get_opcodes()
get_opcodes() describe cómo transformar la primera secuencia en la segunda.
a = "qabxcd"
b = "abycdf"
matcher = SequenceMatcher(None, a, b)
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
print(tag, a[i1:i2], b[j1:j2])Las etiquetas son equal, replace, delete e insert. La salida estructurada resulta útil para interfaces.
Comparar muchas entradas con una referencia
SequenceMatcher guarda información de la segunda secuencia. Configura una referencia una vez cuando varias entradas se comparan con el mismo catálogo.
matcher = SequenceMatcher(None)
matcher.set_seq2("configuración")
for entrada in ["configuracion", "config", "configuraciones"]:
matcher.set_seq1(entrada)
print(entrada, matcher.ratio())Este reaprovechamiento reduce trabajo en correctores y normalizadores.
Sugerencias con get_close_matches()
get_close_matches() devuelve los mejores candidatos por encima de un corte.
from difflib import get_close_matches
comandos = ["instalar", "inspeccionar", "iniciar", "interrumpir"]
print(get_close_matches("instlar", comandos, n=3, cutoff=0.6))Es útil en CLIs, validación y mensajes “quizás quisiste decir”. No es búsqueda semántica porque compara forma, no significado.
La heurística autojunk
En secuencias de al menos 200 elementos, la heurística automática trata elementos muy repetidos como populares y reduce su papel.
matcher = SequenceMatcher(None, seq_a, seq_b, autojunk=False)Desactiva autojunk cuando la repetición tiene significado, como ADN, logs estructurados o códigos. Mide el rendimiento.
Elementos junk personalizados
El primer argumento del constructor puede marcar elementos que no deben servir de anclas.
matcher = SequenceMatcher(
lambda caracter: caracter in " \t",
"nombre = valor",
"nombre=valor",
)Junk guía la coincidencia, pero no elimina diferencias. Si los espacios deben ignorarse por completo, normaliza antes y conserva originales para mostrar.
Diff unificado
unified_diff() produce el formato usado por parches y revisiones.
from difflib import unified_diff
antes = "línea 1\nlínea antigua\nlínea 3\n".splitlines(keepends=True)
despues = "línea 1\nlínea nueva\nlínea 3\n".splitlines(keepends=True)
diff = unified_diff(
antes,
despues,
fromfile="antes.txt",
tofile="despues.txt",
)
print("".join(diff))La función devuelve un generador. Conviértelo en lista solo si necesitas reutilizar o contar la salida.
Conservar finales de línea
Usa splitlines(keepends=True) al comparar archivos. Las líneas de control incluyen terminadores para funcionar con writelines().
Si las entradas no tienen terminadores, pasa lineterm="".
Diff contextual
context_diff() presenta los cambios en bloques antes/después.
from difflib import context_diff
for linea in context_diff(antes, despues, fromfile="a", tofile="b", n=2):
print(linea, end="")n controla las líneas sin cambios alrededor de cada modificación.
Salida detallada con ndiff()
ndiff() añade un prefijo a cada línea:
-: exclusiva de la primera;+: exclusiva de la segunda;: común;?: guía para cambios internos.
from difflib import ndiff
resultado = list(ndiff(antes, despues))
print("".join(resultado))Las líneas ? no estaban en los originales y pueden ser confusas con tabs.
Restaurar textos con restore()
Un delta de ndiff() puede reconstruir cualquiera de las versiones.
from difflib import restore
original = "".join(restore(resultado, 1))
modificado = "".join(restore(resultado, 2))
assert original == "".join(antes)
assert modificado == "".join(despues)La restauración funciona con salida Differ/ndiff, no con cualquier parche unificado.
Differ para comparar líneas
La clase Differ ofrece una salida similar y filtros opcionales.
from difflib import Differ
comparador = Differ()
resultado = comparador.compare(antes, despues)
print("".join(resultado))Differ no pretende generar el delta mínimo. Las coincidencias locales suelen ser más legibles que sincronizaciones distantes.
Informes lado a lado con HtmlDiff
HtmlDiff genera una tabla o documento completo con cambios entre líneas y dentro de ellas.
from difflib import HtmlDiff
html = HtmlDiff(wrapcolumn=80).make_file(
antes,
despues,
fromdesc="Versión anterior",
todesc="Versión nueva",
context=True,
numlines=3,
)
with open("comparacion.html", "w", encoding="utf-8") as archivo:
archivo.write(html)Es útil en revisión editorial y auditoría.
Seguridad de HtmlDiff
fromdesc y todesc se interpretan como HTML sin escapar. Escapa valores del usuario.
from html import escape
titulo = escape(nombre_del_usuario)El informe contiene fragmentos de documentos y puede ser sensible.
Comparar bytes
diff_bytes() ayuda cuando la codificación es desconocida o inconsistente.
from difflib import diff_bytes, unified_diff
a = [b"linea antigua\n"]
b = [b"linea nueva\n"]
resultado = diff_bytes(unified_diff, a, b)
print(b"".join(resultado))Con una codificación conocida, decodificar correctamente sigue siendo mejor.
difflib frente a filecmp
difflib explica diferencias de contenido. El módulo filecmp determina si archivos o árboles parecen iguales y puede usar metadatos.
Usa filecmp.cmp(..., shallow=False) para confirmar igualdad de contenido y genera un informe textual con difflib cuando haya diferencias. dircmp identifica archivos comunes, exclusivos y distintos.
Normalización previa
Según el objetivo, normaliza Unicode, finales de línea, espacios, mayúsculas y campos variables.
import unicodedata
def normalizar(texto: str) -> str:
texto = unicodedata.normalize("NFC", texto)
return "\n".join(linea.rstrip() for linea in texto.splitlines())Normalizar demasiado puede ocultar cambios importantes. Ofrece modos estricto y tolerante.
Comparar JSON y datos estructurados
Comparar JSON crudo puede marcar solo indentación u orden de claves. Haz parsing, serializa con orden estable y compara la salida.
import json
normalizado = json.dumps(objeto, sort_keys=True, indent=2, ensure_ascii=False)Para diferencias semánticas profundas, una herramienta de estructuras puede ser mejor.
Rendimiento y límites
SequenceMatcher puede tener coste cuadrático en el peor caso. Archivos enormes y secuencias repetitivas necesitan límites de tamaño, memoria y tiempo.
No aceptes documentos ilimitados en una API síncrona. Considera herramientas especializadas o trabajos en cola para entradas grandes.
Probar una regla de similitud
def similar(a: str, b: str, limite: float = 0.8) -> bool:
return SequenceMatcher(None, a, b).ratio() >= limite
assert similar("instalar", "instlar")
assert not similar("instalar", "eliminar")Construye un conjunto rotulado real para calibrar el límite. Prueba cadenas vacías, Unicode, repetición, espacios e inversión de argumentos.
Errores frecuentes
- Tratar
ratio()como probabilidad. - Usar difflib como búsqueda semántica.
- Ignorar el orden de argumentos.
- Comparar archivos sin conservar finales de línea.
- Mostrar descripciones sin escape en HtmlDiff.
- Desactivar autojunk sin medir.
- Cargar archivos ilimitados.
- Esperar una edición mínima.
Buenas prácticas
- Elige granularidad de carácter, token o línea.
- Normaliza solo diferencias irrelevantes.
- Calibra cortes con datos reales.
- Usa diffs unificados para herramientas y HTML para personas.
- Escapa metadatos del HTML.
- Define límites de tamaño y tiempo.
- Usa filecmp primero cuando basta igualdad.
- Prueba Unicode, espacios y repetición.
Conclusión
El módulo difflib en Python transforma la comparación de secuencias en informes útiles para personas y programas. SequenceMatcher ofrece bloques, operaciones y puntuaciones; get_close_matches() sugiere alternativas; y las funciones de diff producen formatos unificado, contextual, detallado y HTML.
El resultado mejora cuando la aplicación define qué cambio importa, elige la granularidad correcta y limita entradas grandes. Con normalización controlada, HTML seguro y pruebas de umbral, difflib sirve de base para revisores, validadores, CLIs y herramientas de auditoría.







