Como lidar com palavras acentuadas em processamento de texto
Trabalhar com palavras que contêm acento circunflexo (^) e agudo (´) em Python ou em qualquer pipeline de processamento de texto é algo que parece simples até você tentar normalizar strings e ver todos os acentos sendo removidos sem aviso. A questão central é que o circunflexo e o agudo são marcas diacríticas diferentes e elas se comportam de formas distintas quando você aplica normalização Unicode. O circunflexo aparece em letras como ê, ô, î, e também na letra ç quando combinado com certos códigos, enquanto o agudo aparece em á, é, í, ó, ú. Cada uma dessas combinações pode ser representada de duas maneiras no Unicode: como um caractere precompilado (por exemplo, U+00E9 para é) ou como uma sequência composta por um base + um combining mark (por exemplo, e + U+0301).
palavras com circunflexo e agudo: o que dá errado na prática
O problema que mais encontrei na vida real foi o seguinte. Estava construindo um validador de CPF que precisava normalizar nomes de cidades antes de fazer lookup em um banco de dados. O banco tinha "São José dos Campos" gravado como "Sao Jose dos Campos" e "João Pessoa" como "Joao Pessoa", mas também tinha alguns registros onde o acento circunflexo havia sido perdido e o registro estava como "Curitiba" em vez de algo acentuado. Quando eu aplicava um unicodedata.normalize('NFD', texto), todos os acentos agudos e circunflexos eram separados das letras base. O círculo foi embora, mas os acentos circonflexos às vezes não iam embora se o caractere estivesse na forma NFC ao invés de NFD. Isso gerou um bug onde metade dos matches funcionava e a outra metade falhava, sem nenhum erro explícito. A solução que funcionou foi: normalizar para NFD, remover os combining diacritical marks (categoria Unicode Mn), e depois recompor para NFC. Em código:
import unicodedata
def normalizar(texto):
texto = unicodedata.normalize('NFD', texto)
texto = ''.join(c for c in texto if unicodedata.category(c) != 'Mn')
return unicodedata.normalize('NFC', texto) Isso remove tanto o acento agudo quanto o circunflexo de forma consistente, independentemente de como o caractere chegou no seu sistema. Se o seu texto vier de uma API que já está em NFC, o normalize('NFD') resolve. Se vier de uma fonte caótica como planilhas do Excel antigas, você pode precisar rodar esse processo duas vezes.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Quando a normalização não funciona
Existem casos onde a abordagem acima quebra. Por exemplo, a letra c cedilha (ç) não tem acento nem circunflexo, mas muitas pessoas confundem porque o ç também é um caractere composto visualmente. Ele é U+00E7 e a normalização NFD o decompõe para c + U+0327 (combining cedilla). Se você remover todos os caracteres da categoria Mn, o ç vira simplesmente c. Para manter o ç, você precisa filtrar só os combining marks que são especificamente acento agudo (U+0301) e circunflexo (U+0302). Se o seu objetivo é comparar strings mantendo o ç mas removendo apenas os acentos de vogais, o filtro deve ser assim:
def normalizar_vogais(texto):
texto = unicodedata.normalize('NFD', texto)
texto = ''.join(c for c in texto if not (unicodedata.category(c) == 'Mn' and ord(c) in (0x0301, 0x0302)))
return unicodedata.normalize('NFC', texto) Essa versão transforma "avô" em "avo", "coração" em "coracao", mas mantém "graça" como "graça". Se você está trabalhando com dados brasileiros, essa distinção entre ç e vogais acentuadas faz diferença porque CPF, CNPJ e nomes de cidades frequentemente contêm ç.
Performance e alternativas
A abordagem com unicodedata é suficientemente rápida para a maioria dos casos. Em benchmarks improvisados, processar um arquivo de 50 mil linhas leva cerca de 2 a 3 segundos no meu setup. Se o volume for maior, uma alternativa é usar o pacote unidecode, que converte caracteres Unicode para sua aproximação ASCII de forma mais agressiva. Ele resolve o problema sem você precisar pensar em categorias Unicode, mas tem uma desvantagem clara: ele tenta transliterar tudo, inclusive letras gregas, acentos portugueses, caracteres japoneses, e o resultado pode ser pior do que simplesmente remover os diacríticos manualmente. Outro ponto que poucas pessoas mencionam: se você está fazendo essa normalização para um campo que será indexado no banco de dados, certifique-se de que o collation do banco também está configurado para ignorar acentos. PostgreSQL com o collation padrão C vai tratar "Sao" e "São" como strings diferentes. Use o collation pt_BR com a opção strength=secondary para que os acentos sejam ignorados nas comparações. Sem isso, toda a normalização que você fizer no aplicativo será inútil na hora da query.
Resumo do que funciona
Para remover acento agudo e circunflexo de palavras em português, normalize para NFD, filtre os combining marks U+0301 e U+0302, recomponha para NFC. Mantenha o ç separadamente se necessário. Valide se o banco de dados está usando o collation correto. Teste com dados reais antes de confiar na função, porque fontes de dados malformadas aparecem sempre no pior momento possível.