Sincronizadores, backups, testes de implantação e ferramentas de auditoria precisam descobrir se arquivos ou árvores de diretórios são iguais. Ler cada byte sempre funciona, mas pode ser caro quando há milhares de itens. O módulo filecmp no Python oferece comparações rápidas entre arquivos, lotes e diretórios, com opção de verificar metadados ou conteúdo real.
Neste guia, você aprenderá a usar cmp(), cmpfiles() e dircmp, entenderá o parâmetro shallow, o cache baseado em stat(), a comparação recursiva e quando complementar o resultado com hashes. O conteúdo se conecta aos artigos sobre difflib, tempfile, leitura de arquivos, PermissionError e collections.
Comparação básica com cmp()
filecmp.cmp() recebe dois caminhos e informa se os arquivos parecem iguais.
from filecmp import cmp
iguais = cmp("original.txt", "copia.txt")
print(iguais)O valor padrão shallow=True permite considerar iguais arquivos cuja assinatura de os.stat() coincide. Essa assinatura inclui tipo, tamanho e tempo de modificação.
Comparação superficial
A comparação superficial evita abrir o conteúdo quando os metadados já coincidem.
iguais = cmp("a.bin", "b.bin", shallow=True)É rápida para caches, builds e diretórios controlados, mas não prova que todos os bytes são iguais. Dois arquivos diferentes podem ter o mesmo tamanho e timestamp.
Comparação por conteúdo
Para ler e comparar os bytes, use shallow=False.
iguais = cmp("a.bin", "b.bin", shallow=False)A documentação oficial de filecmp informa que, mesmo quando shallow=False, a função pode reutilizar um resultado armazenado em cache. O cache é invalidado quando as informações de stat() mudam.
O cache e timestamps rápidos
Em sistemas com baixa resolução de timestamp, um arquivo pode ser alterado rapidamente sem que o tempo de modificação aparente mude. Se tamanho e timestamp também permanecerem iguais, um resultado antigo pode ser reutilizado.
import filecmp
filecmp.clear_cache()
iguais = filecmp.cmp("a.txt", "b.txt", shallow=False)clear_cache() é útil em testes ou comparações realizadas imediatamente depois de uma gravação. Em fluxos normais, fechar e sincronizar os arquivos antes da comparação reduz ambiguidades.
Verificando que os caminhos são arquivos
cmp() foi projetada para arquivos regulares. Valide entradas antes de comparar.
from pathlib import Path
from filecmp import cmp
def arquivos_iguais(a, b):
pa = Path(a)
pb = Path(b)
if not pa.is_file() or not pb.is_file():
raise ValueError("Os dois caminhos devem ser arquivos")
return cmp(pa, pb, shallow=False)Links simbólicos, dispositivos e arquivos especiais exigem uma política própria. Decida se deseja comparar o link, o alvo ou rejeitar o item.
Comparando vários nomes com cmpfiles()
cmpfiles() compara arquivos com nomes relativos iguais em dois diretórios.
from filecmp import cmpfiles
nomes = ["app.py", "config.toml", "README.md"]
iguais, diferentes, erros = cmpfiles(
"versao-a",
"versao-b",
nomes,
shallow=False,
)
print("Iguais:", iguais)
print("Diferentes:", diferentes)
print("Erros:", erros)A terceira lista contém arquivos ausentes, inválidos ou que não puderam ser lidos. Não trate erro como simples diferença; registre e investigue a causa.
Tratando PermissionError e arquivos ausentes
Comparações podem falhar por permissões, links quebrados ou concorrência.
from pathlib import Path
for nome in erros:
caminho_a = Path("versao-a") / nome
caminho_b = Path("versao-b") / nome
print(nome, caminho_a.exists(), caminho_b.exists())Uma aplicação de auditoria deve diferenciar ausente, sem permissão, tipo incompatível e alteração durante a leitura.
Analisando diretórios com dircmp
A classe dircmp compara duas árvores e expõe listas e subcomparações.
from filecmp import dircmp
comparacao = dircmp("projeto-a", "projeto-b")
print(comparacao.left_only)
print(comparacao.right_only)
print(comparacao.common_files)
print(comparacao.diff_files)Os atributos são calculados sob demanda, então criar o objeto não percorre necessariamente toda a árvore.
Principais atributos de dircmp
left_listeright_list: entradas de cada lado;common: nomes presentes em ambos;left_onlyeright_only: nomes exclusivos;common_dirs: subdiretórios em comum;common_files: arquivos regulares em comum;common_funny: itens com tipos incompatíveis ou erro destat();same_files,diff_filesefunny_files: resultados da comparação de arquivos comuns.
Use todas as categorias relevantes; verificar apenas diff_files pode ignorar arquivos exclusivos.
Relatórios prontos
report() mostra apenas o nível atual. report_partial_closure() inclui subdiretórios comuns imediatos. report_full_closure() percorre recursivamente.
comparacao.report_full_closure()Esses métodos imprimem em saída padrão. Para aplicações web, JSON ou testes, leia os atributos e construa uma estrutura própria.
Recursão com subdirs
subdirs mapeia cada subdiretório comum para outro objeto dircmp.
def coletar(comp, prefixo=""):
resultado = []
for nome in comp.left_only:
resultado.append(("somente_esquerda", prefixo + nome))
for nome in comp.right_only:
resultado.append(("somente_direita", prefixo + nome))
for nome in comp.diff_files:
resultado.append(("diferente", prefixo + nome))
for nome, sub in comp.subdirs.items():
resultado.extend(coletar(sub, prefixo + nome + "/"))
return resultadoProteja a aplicação contra árvores enormes, profundidade excessiva e alterações concorrentes.
shallow em dircmp
Desde Python 3.13, dircmp aceita o argumento shallow.
comparacao = dircmp("a", "b", shallow=False)Use False quando same_files e diff_files precisam refletir conteúdo, não apenas metadados.
Ignorando nomes
O parâmetro ignore substitui a lista padrão de nomes ignorados.
comparacao = dircmp(
"a",
"b",
ignore=[".git", "__pycache__", ".venv", "node_modules"],
shallow=False,
)Se você fornece a lista, inclua também o que deseja manter ignorado. Não esconda artefatos relevantes por conveniência.
O parâmetro hide
hide controla nomes omitidos dos relatórios, com padrão incluindo . e ...
Em geral, ignore define itens que não participam da comparação, enquanto hide afeta a apresentação.
Comparação textual com difflib
filecmp informa que dois arquivos diferem, mas não explica como. Para texto, gere um diff depois.
from difflib import unified_diff
from pathlib import Path
antes = Path("a/config.ini").read_text(encoding="utf-8").splitlines(True)
depois = Path("b/config.ini").read_text(encoding="utf-8").splitlines(True)
print("".join(unified_diff(antes, depois, fromfile="a", tofile="b")))Em arquivos binários, um diff de linhas não faz sentido; reporte tamanho, hash ou uma ferramenta específica do formato.
Quando usar hashes
Uma comparação byte a byte responde se o conteúdo atual coincide. Um hash persistido permite verificar integridade em outro momento ou máquina.
from hashlib import file_digest
def sha256(caminho):
with open(caminho, "rb") as arquivo:
return file_digest(arquivo, "sha256").hexdigest()A documentação oficial de hashlib disponibiliza SHA-256 e outros algoritmos. Para segurança, evite MD5 e SHA-1 em novas verificações contra adulteração.
Hash não elimina a leitura
Calcular um digest lê todos os bytes e normalmente custa mais que uma comparação que pode parar na primeira diferença. Use hashes quando precisa armazenar, transmitir ou assinar uma impressão digital, não apenas para comparar dois arquivos locais uma única vez.
Condições de corrida
Um arquivo pode mudar entre stat(), leitura e geração do relatório. O resultado descreve um momento aproximado, não um snapshot transacional.
Para backups críticos, trabalhe sobre snapshots do sistema de arquivos, bloqueios apropriados ou cópias temporárias. Depois da comparação, verifique novamente metadados se a consistência for essencial.
Links simbólicos
A resolução de links depende das operações de sistema utilizadas. Uma ferramenta de sincronização precisa definir se preserva links ou compara alvos.
Use Path.is_symlink() e os.readlink() quando a identidade do link importa. Evite seguir links para fora da raiz autorizada.
Arquivos grandes
cmp(..., shallow=False) lê em blocos e não precisa carregar o arquivo inteiro na memória. Mesmo assim, comparar muitos arquivos grandes consome I/O.
Use metadados como filtro inicial, limite concorrência para não saturar o disco e priorize arquivos modificados. Em armazenamento remoto, considere APIs de checksum fornecidas pelo serviço.
Testes com diretórios temporários
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_filesInclua arquivos exclusivos, subdiretórios, erros de permissão, links e alterações rápidas.
Exemplo de auditoria estruturada
def auditar(a, b):
comp = dircmp(a, b, shallow=False)
return {
"somente_a": comp.left_only,
"somente_b": comp.right_only,
"iguais": comp.same_files,
"diferentes": comp.diff_files,
"erros": comp.funny_files + comp.common_funny,
"subdiretorios": {
nome: auditar(sub.left, sub.right)
for nome, sub in comp.subdirs.items()
},
}Em árvores não confiáveis, imponha limite de profundidade e quantidade de itens.
Erros frequentes
- Usar
shallow=Truecomo prova criptográfica de igualdade. - Ignorar
left_onlyeright_only. - Tratar arquivos com erro como diferentes comuns.
- Esquecer o cache após gravações muito rápidas.
- Comparar links simbólicos sem definir a política.
- Calcular hash para tudo sem considerar custo.
- Presumir que a árvore não muda durante a análise.
- Imprimir relatórios quando a aplicação precisa de dados estruturados.
Boas práticas
- Escolha conscientemente comparação superficial ou por conteúdo.
- Use
cmpfiles()para lotes com nomes conhecidos. - Use
dircmppara estrutura e recursão. - Classifique ausências, diferenças e erros separadamente.
- Use difflib para explicar diferenças textuais.
- Use SHA-256 quando precisa de uma impressão digital persistente.
- Limite profundidade, número de arquivos e concorrência.
- Teste alterações rápidas e comportamento multiplataforma.
Conclusão
O módulo filecmp no Python fornece uma base simples para comparar arquivos e diretórios. cmp() trabalha com dois arquivos, cmpfiles() classifica um lote e dircmp revela diferenças estruturais e recursivas entre árvores.
A confiabilidade depende da opção escolhida. Metadados oferecem velocidade; conteúdo oferece uma confirmação mais forte; hashes permitem persistir uma impressão digital. Ao separar diferenças de erros, considerar o cache, definir políticas para links e lidar com concorrência, você pode construir verificadores de implantação, backups e auditorias que produzem resultados claros sem ler mais dados do que o necessário.






