Los sincronizadores, backups, pruebas de despliegue y herramientas de auditoría necesitan descubrir si archivos o árboles de directorios son iguales. Leer cada byte siempre funciona, pero puede ser costoso con miles de elementos. El módulo filecmp en Python ofrece comparación eficiente de archivos individuales, lotes y directorios, con opción de verificar metadatos o contenido real.
Esta guía explica cmp(), cmpfiles() y dircmp, incluido el parámetro shallow, la caché basada en stat(), la comparación recursiva, los enlaces simbólicos, la concurrencia y cuándo añadir hashes. Complementa nuestros artículos sobre difflib, archivos temporales, lectura de archivos, PermissionError y collections.
Comparación básica con cmp()
filecmp.cmp() recibe dos rutas e indica si los archivos parecen iguales.
from filecmp import cmp
iguales = cmp("original.txt", "copia.txt")
print(iguales)El valor predeterminado shallow=True permite considerar iguales los archivos cuya firma de os.stat() coincide. Esa firma incluye tipo, tamaño y fecha de modificación.
Comparación superficial
Una comparación superficial evita leer el contenido cuando los metadatos ya coinciden.
iguales = cmp("a.bin", "b.bin", shallow=True)Es rápida para cachés, builds y directorios controlados, pero no demuestra que todos los bytes sean iguales. Dos archivos diferentes pueden tener el mismo tamaño y timestamp.
Comparación por contenido
Usa shallow=False para comparar los bytes.
iguales = cmp("a.bin", "b.bin", shallow=False)La documentación oficial de filecmp indica que la función aún puede reutilizar un resultado en caché. La entrada se invalida cuando cambia la información de stat().
Caché y escrituras rápidas
En sistemas de archivos con poca resolución temporal, un archivo puede reescribirse rápidamente sin cambio visible en la fecha. Si el tamaño también permanece igual, podría reutilizarse un resultado antiguo.
import filecmp
filecmp.clear_cache()
iguales = filecmp.cmp("a.txt", "b.txt", shallow=False)clear_cache() es útil en pruebas y justo después de escrituras rápidas. Cerrar y ejecutar flush antes de comparar reduce ambigüedades.
Validar archivos regulares
cmp() está diseñada para archivos regulares. Valida las rutas.
from pathlib import Path
from filecmp import cmp
def archivos_iguales(a, b):
pa = Path(a)
pb = Path(b)
if not pa.is_file() or not pb.is_file():
raise ValueError("Ambas rutas deben ser archivos")
return cmp(pa, pb, shallow=False)Los enlaces, dispositivos y archivos especiales necesitan una política. Decide si comparas el enlace, su destino o lo rechazas.
Comparar varios nombres con cmpfiles()
cmpfiles() compara archivos con el mismo nombre relativo en dos directorios.
from filecmp import cmpfiles
nombres = ["app.py", "config.toml", "README.md"]
iguales, diferentes, errores = cmpfiles(
"version-a",
"version-b",
nombres,
shallow=False,
)
print("Iguales:", iguales)
print("Diferentes:", diferentes)
print("Errores:", errores)La lista de errores contiene archivos ausentes, inválidos o ilegibles. No trates un error como una diferencia ordinaria; informa la causa.
Archivos ausentes y permisos
La comparación puede fallar por permisos, enlaces rotos o cambios concurrentes.
from pathlib import Path
for nombre in errores:
ruta_a = Path("version-a") / nombre
ruta_b = Path("version-b") / nombre
print(nombre, ruta_a.exists(), ruta_b.exists())Una auditoría debe distinguir ausente, sin permiso, tipo incompatible y cambiado durante la lectura.
Análisis de directorios con dircmp
La clase dircmp compara dos árboles y expone listas y comparaciones anidadas.
from filecmp import dircmp
comparacion = dircmp("proyecto-a", "proyecto-b")
print(comparacion.left_only)
print(comparacion.right_only)
print(comparacion.common_files)
print(comparacion.diff_files)Los atributos se calculan bajo demanda. Crear el objeto no recorre necesariamente todo el árbol.
Atributos principales de dircmp
left_listyright_list: entradas de cada lado;common: nombres presentes en ambos;left_onlyyright_only: nombres exclusivos;common_dirs: subdirectorios compartidos;common_files: archivos regulares comunes;common_funny: tipos incompatibles o fallos destat();same_files,diff_filesyfunny_files: resultados para archivos comunes.
Comprobar solo diff_files omite archivos exclusivos.
Informes integrados
report() imprime el nivel actual. report_partial_closure() añade subdirectorios inmediatos y report_full_closure() recorre todo.
comparacion.report_full_closure()Estos métodos imprimen en salida estándar. Una API o prueba debe consumir los atributos y construir datos estructurados.
Recursión mediante subdirs
subdirs asocia cada subdirectorio común con otro objeto dircmp.
def recoger(comp, prefijo=""):
resultado = []
for nombre in comp.left_only:
resultado.append(("solo_izquierda", prefijo + nombre))
for nombre in comp.right_only:
resultado.append(("solo_derecha", prefijo + nombre))
for nombre in comp.diff_files:
resultado.append(("diferente", prefijo + nombre))
for nombre, hijo in comp.subdirs.items():
resultado.extend(recoger(hijo, prefijo + nombre + "/"))
return resultadoProtege la aplicación contra árboles enormes, profundidad excesiva y modificaciones concurrentes.
shallow en dircmp
Desde Python 3.13, dircmp acepta shallow.
comparacion = dircmp("a", "b", shallow=False)Usa False cuando same_files y diff_files deben reflejar contenido real.
Ignorar nombres
El parámetro ignore sustituye la lista predeterminada.
comparacion = dircmp(
"a",
"b",
ignore=[".git", "__pycache__", ".venv", "node_modules"],
shallow=False,
)Si proporcionas una lista, incluye todo lo que debe continuar ignorado. No ocultes artefactos relevantes solo para simplificar el informe.
El parámetro hide
hide controla nombres omitidos de los informes, con valores por defecto como . y ...
En general, ignore elimina elementos de la comparación, mientras hide afecta la presentación.
Explicar diferencias textuales con difflib
filecmp informa que dos archivos difieren, pero no explica cómo. Genera un diff después.
from difflib import unified_diff
from pathlib import Path
antes = Path("a/config.ini").read_text(encoding="utf-8").splitlines(True)
despues = Path("b/config.ini").read_text(encoding="utf-8").splitlines(True)
print("".join(unified_diff(antes, despues, fromfile="a", tofile="b")))Para binarios, informa tamaño, hash o utiliza una herramienta del formato.
Cuándo usar hashes
Una comparación byte a byte responde si el contenido actual coincide. Un digest persistido permite verificar integridad después o en otra máquina.
from hashlib import file_digest
def sha256(ruta):
with open(ruta, "rb") as archivo:
return file_digest(archivo, "sha256").hexdigest()La documentación oficial de hashlib ofrece SHA-256 y otros algoritmos. Evita MD5 y SHA-1 para nuevos diseños contra manipulación.
Un hash también lee el archivo
Calcular un digest lee todos los bytes y puede costar más que una comparación que se detiene en la primera diferencia. Usa hashes cuando necesitas guardar, transmitir, firmar o comparar repetidamente una huella.
Condiciones de carrera
Un archivo puede cambiar entre stat(), lectura e informe. El resultado describe un momento aproximado, no un snapshot transaccional.
Los backups críticos deben usar snapshots, bloqueos adecuados o copias temporales. Revisa de nuevo los metadatos si la consistencia es esencial.
Enlaces simbólicos
La resolución depende de las operaciones del sistema. Un sincronizador debe definir si conserva enlaces o compara destinos.
Usa Path.is_symlink() y os.readlink() cuando importa la identidad del enlace. Impide que enlaces seguidos salgan de una raíz autorizada.
Archivos y árboles grandes
cmp(..., shallow=False) lee por bloques y no carga todo en memoria. Aun así, comparar muchos archivos grandes consume I/O.
Usa metadatos como filtro inicial, limita concurrencia y prioriza elementos modificados. Los almacenamientos remotos pueden ofrecer checksums del servidor.
Pruebas con directorios temporales
from pathlib import Path
from tempfile import TemporaryDirectory
from filecmp import dircmp
with TemporaryDirectory() as a, TemporaryDirectory() as b:
Path(a, "igual.txt").write_text("ok", encoding="utf-8")
Path(b, "igual.txt").write_text("ok", encoding="utf-8")
Path(a, "diferente.txt").write_text("A", encoding="utf-8")
Path(b, "diferente.txt").write_text("B", encoding="utf-8")
comp = dircmp(a, b, shallow=False)
assert "igual.txt" in comp.same_files
assert "diferente.txt" in comp.diff_filesPrueba también elementos exclusivos, carpetas, permisos, enlaces y escrituras rápidas.
Ejemplo de auditoría estructurada
def auditar(a, b):
comp = dircmp(a, b, shallow=False)
return {
"solo_a": comp.left_only,
"solo_b": comp.right_only,
"iguales": comp.same_files,
"diferentes": comp.diff_files,
"errores": comp.funny_files + comp.common_funny,
"subdirectorios": {
nombre: auditar(hijo.left, hijo.right)
for nombre, hijo in comp.subdirs.items()
},
}En árboles no confiables, impone límites de profundidad y cantidad.
Errores frecuentes
- Usar
shallow=Truecomo prueba criptográfica. - Ignorar
left_onlyyright_only. - Tratar archivos ilegibles como diferencias normales.
- Olvidar la caché tras escrituras rápidas.
- Comparar enlaces sin una política.
- Calcular hash para todo sin considerar el coste.
- Suponer que el árbol no cambia durante el análisis.
- Imprimir informes cuando se necesitan datos estructurados.
Buenas prácticas
- Elige conscientemente metadatos o contenido.
- Usa
cmpfiles()para lotes conocidos. - Usa
dircmppara estructura y recursión. - Clasifica ausencias, diferencias y errores por separado.
- Usa difflib para explicar cambios textuales.
- Usa SHA-256 cuando necesitas una huella persistente.
- Limita profundidad, cantidad y concurrencia.
- Prueba cambios rápidos y varias plataformas.
Conclusión
El módulo filecmp en Python ofrece una base sencilla para comparar archivos y directorios. cmp() maneja dos archivos, cmpfiles() clasifica un lote y dircmp revela diferencias estructurales y recursivas.
La fiabilidad depende del nivel elegido. Los metadatos aportan velocidad, el contenido ofrece una confirmación más fuerte y los hashes permiten conservar una huella. Al separar errores de diferencias, considerar la caché, definir políticas para enlaces y manejar concurrencia, puedes crear verificadores de despliegue, backups y auditorías con resultados claros.






