Comparar duas versões de um texto é útil em revisão, auditoria, migrações, testes, sincronização e ferramentas de linha de comando. Em vez de apenas descobrir que os conteúdos são diferentes, muitas aplicações precisam mostrar quais linhas foram removidas, adicionadas ou alteradas. O módulo difflib no Python oferece comparadores de sequências, diffs legíveis, sugestões por similaridade e relatórios HTML sem dependências externas.
Neste guia, você aprenderá a usar SequenceMatcher, get_close_matches(), unified_diff(), context_diff(), ndiff(), restore() e HtmlDiff. O conteúdo complementa nossos artigos sobre listas em Python, slicing, arquivos temporários, collections e leitura de arquivos de texto.
O que difflib compara
difflib trabalha com sequências cujos elementos são hashable. Isso inclui strings, listas de linhas e listas de tokens.
from difflib import SequenceMatcher
antigo = "Python é simples"
novo = "Python é muito simples"
matcher = SequenceMatcher(None, antigo, novo)
print(matcher.ratio())O algoritmo procura blocos contíguos comuns e aplica a mesma ideia recursivamente às partes restantes. Ele prioriza correspondências que parecem naturais para pessoas, mas não promete a sequência mínima de edições.
Similaridade com ratio()
ratio() retorna um valor entre 0 e 1. Valores próximos de 1 indicam grande semelhança.
from difflib import SequenceMatcher
similaridade = SequenceMatcher(None, "configuracao", "configuração").ratio()
print(round(similaridade, 3))Esse número não é uma probabilidade e não possui um limiar universal. Escolha o corte com dados reais, avaliando falsos positivos e falsos negativos.
A ordem dos argumentos pode influenciar
A documentação oficial de difflib alerta que ratio() pode produzir valores diferentes ao inverter as sequências.
print(SequenceMatcher(None, "tide", "diet").ratio())
print(SequenceMatcher(None, "diet", "tide").ratio())Se sua regra precisa ser simétrica, calcule nas duas direções ou use uma métrica desenhada explicitamente para isso.
quick_ratio() e real_quick_ratio()
Esses métodos retornam limites superiores mais rápidos para ratio().
matcher = SequenceMatcher(None, "abcd", "bcde")
print(matcher.real_quick_ratio())
print(matcher.quick_ratio())
print(matcher.ratio())Em uma busca grande, você pode descartar candidatos cuja estimativa já fica abaixo do limiar e calcular a razão completa apenas para os restantes.
Encontrando blocos correspondentes
get_matching_blocks() informa posições e tamanhos dos blocos iguais.
matcher = SequenceMatcher(None, "abxcd", "abcd")
for bloco in matcher.get_matching_blocks():
print(bloco)O último bloco é sempre uma sentinela de tamanho zero. Essa API ajuda a destacar trechos ou construir visualizações próprias.
Transformações com get_opcodes()
get_opcodes() descreve como transformar a primeira sequência na 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])As tags possíveis são equal, replace, delete e insert. Essa saída é mais estruturada que um diff textual e pode alimentar uma interface gráfica.
Comparando um item com muitos
SequenceMatcher armazena informações da segunda sequência. Quando um catálogo fixo será comparado com várias entradas, configure-o uma vez.
matcher = SequenceMatcher(None)
matcher.set_seq2("configuração")
for entrada in ["configuracao", "config", "configurações"]:
matcher.set_seq1(entrada)
print(entrada, matcher.ratio())Esse reaproveitamento pode reduzir trabalho em corretores de digitação e normalizadores.
Sugestões com get_close_matches()
get_close_matches() retorna os candidatos mais parecidos acima de um corte.
from difflib import get_close_matches
comandos = ["iniciar", "instalar", "inspecionar", "interromper"]
print(get_close_matches("instlar", comandos, n=3, cutoff=0.6))É útil em CLIs, validação de campos e mensagens “você quis dizer?”. Não use como busca semântica: a função compara forma da sequência, não significado.
O parâmetro autojunk
Em sequências com pelo menos 200 elementos, a heurística automática trata itens muito repetidos como “populares” e reduz seu peso.
matcher = SequenceMatcher(None, seq_a, seq_b, autojunk=False)Desative autojunk quando a repetição é significativa, como DNA, logs estruturados ou listas de códigos. Compare desempenho e qualidade, pois desativar pode aumentar custo.
Elementos considerados junk
O primeiro argumento de SequenceMatcher pode ser uma função que marca elementos sem interesse.
matcher = SequenceMatcher(
lambda caractere: caractere in " \t",
"nome = valor",
"nome=valor",
)Junk ajuda a encontrar pontos de sincronização, mas não remove diferenças da saída. Se espaços precisam ser totalmente ignorados, normalize os textos antes de comparar e preserve uma cópia original para exibição.
Diff unificado
unified_diff() produz o formato conhecido de patches e revisões de código.
from difflib import unified_diff
antes = "linha 1\nlinha antiga\nlinha 3\n".splitlines(keepends=True)
depois = "linha 1\nlinha nova\nlinha 3\n".splitlines(keepends=True)
diff = unified_diff(
antes,
depois,
fromfile="antes.txt",
tofile="depois.txt",
)
print("".join(diff))A função retorna um gerador. Materialize em lista apenas quando realmente precisar reutilizar ou contar as linhas.
Preservando quebras de linha
Use splitlines(keepends=True) ao comparar arquivos. As linhas de controle do diff usam quebra de linha por padrão para funcionar com writelines().
Quando as entradas não possuem terminadores, passe lineterm="" para evitar uma mistura de linhas com e sem quebra.
Diff contextual
context_diff() apresenta mudanças em blocos antes/depois.
from difflib import context_diff
for linha in context_diff(antes, depois, fromfile="a", tofile="b", n=2):
print(linha, end="")O parâmetro n controla quantas linhas iguais cercam cada mudança. Um contexto maior ajuda revisão, mas aumenta o relatório.
ndiff() para diferenças detalhadas
ndiff() marca cada linha com um prefixo:
-: exclusiva da primeira sequência;+: exclusiva da segunda;: comum;?: guia visual para diferenças internas.
from difflib import ndiff
resultado = list(ndiff(antes, depois))
print("".join(resultado))Linhas iniciadas por ? não existem nos textos originais e podem ficar confusas com tabs e espaços.
Restaurando textos com restore()
Um delta de ndiff() pode restaurar qualquer uma das versões.
from difflib import restore
original = "".join(restore(resultado, 1))
modificado = "".join(restore(resultado, 2))
assert original == "".join(antes)
assert modificado == "".join(depois)Essa restauração vale para o formato Differ/ndiff, não para qualquer diff unificado arbitrário.
Differ para comparação linha a linha
A classe Differ oferece a mesma família de saída e permite filtros de linha e caractere.
from difflib import Differ
comparador = Differ()
resultado = comparador.compare(antes, depois)
print("".join(resultado))O resultado não pretende ser o patch mínimo. Correspondências locais geralmente são mais legíveis que sincronizações distantes e acidentais.
Relatório lado a lado com HtmlDiff
HtmlDiff gera uma tabela ou documento completo com mudanças entre linhas e dentro das linhas.
from difflib import HtmlDiff
html = HtmlDiff(wrapcolumn=80).make_file(
antes,
depois,
fromdesc="Versão anterior",
todesc="Versão nova",
context=True,
numlines=3,
)
with open("comparacao.html", "w", encoding="utf-8") as arquivo:
arquivo.write(html)Esse formato é útil em revisão editorial e relatórios de auditoria.
Segurança no HtmlDiff
fromdesc e todesc são interpretados como HTML sem escape. Se vierem de usuários, aplique html.escape().
from html import escape
titulo = escape(nome_enviado_pelo_usuario)Trate também o arquivo gerado como conteúdo potencialmente sensível, pois ele inclui trechos dos documentos comparados.
Comparando bytes
diff_bytes() ajuda quando a codificação é desconhecida ou inconsistente.
from difflib import diff_bytes, unified_diff
a = [b"linha antiga\n"]
b = [b"linha nova\n"]
resultado = diff_bytes(unified_diff, a, b)
print(b"".join(resultado))A função converte internamente sem perda e devolve bytes. Para texto com codificação conhecida, decodificar corretamente continua sendo preferível.
difflib versus filecmp
difflib explica diferenças de conteúdo. O módulo filecmp responde se arquivos ou árvores parecem iguais e pode usar metadados para uma comparação superficial.
Use filecmp.cmp(..., shallow=False) para confirmar conteúdo e, quando houver divergência textual, gere um relatório com difflib. Para diretórios, dircmp identifica arquivos comuns, exclusivos e diferentes.
Normalização antes da comparação
Dependendo do objetivo, normalize Unicode, finais de linha, espaços, caixa e campos voláteis.
import unicodedata
def normalizar(texto: str) -> str:
texto = unicodedata.normalize("NFC", texto)
return "\n".join(linha.rstrip() for linha in texto.splitlines())Normalizar demais pode esconder mudanças importantes. Mantenha duas modalidades: comparação estrita e comparação tolerante.
Comparando JSON e dados estruturados
Comparar JSON bruto pode marcar apenas mudanças de indentação ou ordem de chaves. Faça parsing, serialize com ordenação estável e compare a saída normalizada.
import json
normalizado = json.dumps(objeto, sort_keys=True, indent=2, ensure_ascii=False)Para diferenças semânticas profundas, uma ferramenta específica de estrutura pode ser melhor.
Desempenho e limites
SequenceMatcher pode ter custo quadrático no pior caso. Arquivos enormes ou sequências altamente repetitivas exigem limites de tamanho, tempo e memória.
Não envie documentos ilimitados para uma comparação síncrona de API. Considere streaming, amostragem, ferramentas externas especializadas ou filas de trabalho.
Testando um comparador
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", "remover")Crie um conjunto rotulado com casos reais para calibrar o limite. Teste textos vazios, Unicode, repetição, espaços e inversão dos argumentos.
Erros frequentes
- Tratar
ratio()como probabilidade. - Usar difflib como busca semântica.
- Ignorar a influência da ordem dos argumentos.
- Comparar arquivos sem preservar finais de linha.
- Exibir descrições não escapadas em HtmlDiff.
- Desativar autojunk sem medir desempenho.
- Carregar arquivos gigantes sem limite.
- Esperar que o diff seja uma sequência mínima de edições.
Boas práticas
- Escolha a granularidade: caractere, token ou linha.
- Normalize apenas diferenças irrelevantes ao domínio.
- Calibre cortes com exemplos reais.
- Use diffs unificados para ferramentas e HTML para pessoas.
- Escape metadados inseridos no HTML.
- Defina limites de tamanho e tempo.
- Use filecmp antes quando basta saber se arquivos são iguais.
- Teste Unicode, whitespace e sequências repetitivas.
Conclusão
O módulo difflib no Python transforma comparação de sequências em relatórios úteis para pessoas e programas. SequenceMatcher oferece blocos, operações e índices de similaridade; get_close_matches() sugere alternativas; e as funções de diff produzem formatos unificado, contextual, detalhado e HTML.
O resultado melhora quando a aplicação define claramente o que é uma mudança relevante, escolhe a granularidade correta e limita entradas grandes. Com normalização controlada, segurança no HTML e testes de limiar, difflib serve como base para revisores, validadores, CLIs e ferramentas de auditoria sem depender de um sistema externo de diff.







