Qual é a melhor maneira de remover acentos em uma string unicode Python?

507

Eu tenho uma string Unicode em Python e gostaria de remover todos os acentos (diacríticos).

Encontrei na Web uma maneira elegante de fazer isso em Java:

  1. converta a cadeia Unicode em sua forma normalizada longa (com um caractere separado para letras e diacríticos)
  2. remova todos os caracteres cujo tipo Unicode é "diacrítico".

Preciso instalar uma biblioteca como pyICU ou isso é possível apenas com a biblioteca padrão python? E o python 3?

Nota importante: eu gostaria de evitar código com um mapeamento explícito de caracteres acentuados para o seu equivalente não acentuado.

MiniQuark
fonte

Respostas:

448

Unidecode é a resposta correta para isso. Ele translitera qualquer string unicode na representação mais próxima possível no texto ascii.

Exemplo:

accented_string = u'Málaga'
# accented_string is of type 'unicode'
import unidecode
unaccented_string = unidecode.unidecode(accented_string)
# unaccented_string contains 'Malaga'and is of type 'str'
Christian Oudard
fonte
67
Parece funcionar bem com o chinês, mas a transformação do nome francês "François" infelizmente dá "FranASSois", o que não é muito bom, comparado aos "françois" mais naturais.
Eric O Lebigot
10
depende do que você está tentando alcançar. por exemplo, estou fazendo uma pesquisa agora e não quero transliterar grego / russo / chinês, só quero substituir "ą / ę / ś / ć" por "a / e / s / c"
kolinko 31/03
58
O unidecode do @EOL funciona muito bem para strings como "François", se você passar objetos unicode para ele. Parece que você tentou com uma sequência de bytes simples.
Karl Bartel
26
Observe que o unidecode> = 0.04.10 (dezembro de 2012) é GPL. Use versões anteriores ou consulte github.com/kmike/text-unidecode se precisar de uma licença mais permissiva e puder suportar uma implementação um pouco pior.
Mikhail Korobov 23/02
10
unidecodesubstitui °por deg. Faz mais do que apenas remover acentos.
Eric Duminil
274

Que tal agora:

import unicodedata
def strip_accents(s):
   return ''.join(c for c in unicodedata.normalize('NFD', s)
                  if unicodedata.category(c) != 'Mn')

Isso funciona também com letras gregas:

>>> strip_accents(u"A \u00c0 \u0394 \u038E")
u'A A \u0394 \u03a5'
>>> 

A categoria de personagem "Mn" significa Nonspacing_Mark, que é semelhante a unicodedata.combining na resposta do MiniQuark (eu não pensei em unicodedata.combining, mas provavelmente é a melhor solução, porque é mais explícita).

E lembre-se, essas manipulações podem alterar significativamente o significado do texto. Sotaques, trema etc. não são "decoração".

oefe
fonte
6
Infelizmente, esses caracteres não são compostos - mesmo que "ł" seja nomeado "LETRA LATINA PEQUENA L COM CURSO"! Você precisará jogar com a análise unicodedata.nameou dividir e usar uma tabela parecida - que você precisaria para as letras gregas de qualquer maneira (Α é apenas "ALFA DE LETRAS DE CAPITAL GREEK").
Alexis
2
@andi, receio que não consiga adivinhar que argumento você quer fazer. A troca de email reflete o que escrevi acima: Como a letra "ł" não é acentuada (e não é tratada como uma no padrão Unicode), ela não possui decomposição.
Alexis23
2
@alexis (acompanhamento tardio): isso também funciona perfeitamente para o grego - por exemplo. "LETRA CAPITAL CAPITAL GREGA COM DASIA E VARIA" é normalizada em "LETTA CAPITAL CAPITAL GREGA ALFA" exatamente como esperado. A menos que você esteja se referindo à transliteração (por exemplo, "α" → "a"), que não é o mesmo que "remover acentos" ...
lenz 16/16
@lenz, eu não estava falando sobre remover sotaques do grego, mas sobre o "golpe" no ell. Como não é um diacrítico, alterá-lo para ell simples é o mesmo que alterar o alfa grego para A. Se não quiser, não faça, mas nos dois casos você substitui um latino (quase) parecido.
Alexis
Principalmente funciona bem :) Mas não se transforma ßem ascii ssno exemplo. Eu ainda usaria unidecodepara evitar acidentes.
Art
146

Acabei de encontrar esta resposta na Web:

import unicodedata

def remove_accents(input_str):
    nfkd_form = unicodedata.normalize('NFKD', input_str)
    only_ascii = nfkd_form.encode('ASCII', 'ignore')
    return only_ascii

Funciona bem (em francês, por exemplo), mas acho que o segundo passo (remover os acentos) poderia ser melhor do que soltar os caracteres não-ASCII, porque isso falhará em alguns idiomas (grego, por exemplo). A melhor solução provavelmente seria remover explicitamente os caracteres unicode marcados como sendo diacríticos.

Edit : isso faz o truque:

import unicodedata

def remove_accents(input_str):
    nfkd_form = unicodedata.normalize('NFKD', input_str)
    return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])

unicodedata.combining(c)retornará true se o caractere cpuder ser combinado com o caractere anterior, ou seja, se for um diacrítico.

Edição 2 : remove_accentsespera uma sequência unicode , não uma sequência de bytes. Se você tiver uma cadeia de bytes, decodifique-a em uma cadeia unicode como esta:

encoding = "utf-8" # or iso-8859-15, or cp1252, or whatever encoding you use
byte_string = b"café"  # or simply "café" before python 3.
unicode_string = byte_string.decode(encoding)
MiniQuark
fonte
5
Eu tive que adicionar 'utf8' ao unicode:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba
@Jabba: , 'utf8'é uma "rede de segurança" necessária se você estiver testando a entrada no terminal (que por padrão não usa unicode). Mas geralmente você não precisa adicioná-lo, pois se você estiver removendo sotaques, input_strprovavelmente já será utf8. Não faz mal estar seguro, no entanto.
precisa saber é o seguinte
1
@rbp: você deve passar uma string unicode para em remove_accentsvez de uma string regular (u "é" em vez de "é"). Você passou uma sequência regular para remove_accents, portanto, ao tentar converter sua sequência em uma sequência unicode, a asciicodificação padrão foi usada. Essa codificação não suporta nenhum byte cujo valor seja> 127. Quando você digitou "é" no seu shell, seu sistema operacional codificou isso, provavelmente com UTF-8 ou alguma codificação da Página de Código do Windows, e isso incluía bytes> 127. Mudarei minha função para remover a conversão para unicode: ela bombardeará mais claramente se uma string não unicode for passada.
MiniQuark
1
@MiniQuark que funcionou perfeitamente >>> remove_accents (unicode ('é'))
rbp
1
Essa resposta me deu o melhor resultado em um grande conjunto de dados, a única exceção é "ð" - os dados unicoded não tocariam!
S29 de
43

Na verdade, eu trabalho no projeto compatível com python 2.6, 2.7 e 3.4 e tenho que criar IDs a partir de entradas gratuitas do usuário.

Graças a você, eu criei esta função que faz maravilhas.

import re
import unicodedata

def strip_accents(text):
    """
    Strip accents from input String.

    :param text: The input string.
    :type text: String.

    :returns: The processed String.
    :rtype: String.
    """
    try:
        text = unicode(text, 'utf-8')
    except (TypeError, NameError): # unicode is a default on python 3 
        pass
    text = unicodedata.normalize('NFD', text)
    text = text.encode('ascii', 'ignore')
    text = text.decode("utf-8")
    return str(text)

def text_to_id(text):
    """
    Convert input text to id.

    :param text: The input string.
    :type text: String.

    :returns: The processed String.
    :rtype: String.
    """
    text = strip_accents(text.lower())
    text = re.sub('[ ]+', '_', text)
    text = re.sub('[^0-9a-zA-Z_-]', '', text)
    return text

resultado:

text_to_id("Montréal, über, 12.89, Mère, Françoise, noël, 889")
>>> 'montreal_uber_1289_mere_francoise_noel_889'
hexaJer
fonte
2
Com o Py2.7, passando erros de string já unicode em text = unicode(text, 'utf-8'). Uma solução alternativa para isso era adicionarexcept TypeError: pass
Daniel Reis
Muito barulho! Trabalhou no meu caso. Uma seleção de poesia brasileira para desenvolver a capacidade de escuta dos alunos idioma Português.
Aaron
23

Isso lida não apenas com detalhes, mas também com "traços" (como em ø etc.):

import unicodedata as ud

def rmdiacritics(char):
    '''
    Return the base character of char, by "removing" any
    diacritics like accents or curls and strokes and the like.
    '''
    desc = ud.name(char)
    cutoff = desc.find(' WITH ')
    if cutoff != -1:
        desc = desc[:cutoff]
        try:
            char = ud.lookup(desc)
        except KeyError:
            pass  # removing "WITH ..." produced an invalid name
    return char

Esta é a maneira mais elegante em que consigo pensar (e foi mencionada por alexis em um comentário nesta página), embora eu não ache que seja muito elegante. De fato, é mais um truque, como apontado nos comentários, já que os nomes Unicode são - na verdade, apenas nomes, eles não dão garantia de consistência nem nada.

Ainda existem letras especiais que não são manipuladas por isso, como letras viradas e invertidas, pois o nome do unicode não contém 'WITH'. Depende do que você deseja fazer de qualquer maneira. Às vezes, eu precisava de remoção de sotaque para obter a ordem de classificação do dicionário.

EDITAR NOTA:

Sugestões incorporadas dos comentários (manipulação de erros de pesquisa, código Python-3).

lenz
fonte
8
Você deve capturar a exceção se o novo símbolo não existir. Por exemplo, há QUADRADO COM PREENCHIMENTO VERTICAL ▥, mas não há QUADRADO. (sem mencionar que esse código transforma o GUARDA-CHUVA COM GOTAS DE CHUVA ☔ em GUARDA-CHUVA ☂).
precisa saber é
Isso parece elegante ao aproveitar as descrições semânticas dos caracteres disponíveis. Nós realmente precisamos da unicodechamada de função lá com python 3? Eu acho que uma expressão regular mais rigorosa no lugar de findevitaria todos os problemas mencionados no comentário acima e também a memorização ajudaria o desempenho quando for um caminho de código crítico.
matanster
1
@matanster não, esta é uma resposta antiga da era Python-2; o unicodetypecast não é mais apropriado no Python 3. De qualquer forma, na minha experiência, não há uma solução universal e elegante para esse problema. Dependendo da aplicação, qualquer abordagem tem seus prós e contras. Ferramentas de qualidade próspera, como unidecodesão baseadas em tabelas artesanais. Alguns recursos (tabelas, algoritmos) são fornecidos pelo Unicode, por exemplo. para agrupamento.
Lenz
1
Eu só repetir, o que está acima (PY3): 1) unicode (char) -> Char 2) tentar: retornar ud.lookup (desc), exceto KeyError: Retornar Char
mirek
@mirek você está certo: como esse tópico é tão popular, esta resposta merece alguma atualização / aprimoramento. Eu editei.
lenz
15

Em resposta à resposta da @ MiniQuark:

Eu estava tentando ler um arquivo CSV que era meio francês (contendo acentos) e também algumas seqüências que acabariam se tornando números inteiros e flutuantes. Como teste, criei um test.txtarquivo parecido com este:

Montreal, über, 12,89, Senhora, Françoise, noël, 889

Eu tive que incluir linhas 2e 3fazê-lo funcionar (que encontrei em um ticket em python), além de incorporar o comentário de @ Jabba:

import sys 
reload(sys) 
sys.setdefaultencoding("utf-8")
import csv
import unicodedata

def remove_accents(input_str):
    nkfd_form = unicodedata.normalize('NFKD', unicode(input_str))
    return u"".join([c for c in nkfd_form if not unicodedata.combining(c)])

with open('test.txt') as f:
    read = csv.reader(f)
    for row in read:
        for element in row:
            print remove_accents(element)

O resultado:

Montreal
uber
12.89
Mere
Francoise
noel
889

(Nota: estou no Mac OS X 10.8.4 e usando o Python 2.7.3)

aseagram
fonte
1
remove_accentsfoi criado para remover acentos de uma string unicode. Caso tenha passado uma sequência de bytes, ele tenta convertê-la em uma sequência unicode unicode(input_str). Isso usa a codificação padrão do python, que é "ascii". Como seu arquivo é codificado com UTF-8, isso falhará. As linhas 2 e 3 alteram a codificação padrão do python para UTF-8, para que funcione como você descobriu. Outra opção é passar remove_accentsuma string unicode: remova as linhas 2 e 3 e, na última linha, substitua elementpor element.decode("utf-8"). Eu testei: funciona. Vou atualizar minha resposta para deixar isso mais claro.
MiniQuark
Boa edição, bom argumento. (Em outra nota: O verdadeiro problema que eu percebi é que meu arquivo de dados é aparentemente codificado em iso-8859-1, que eu não posso começar a trabalhar com esta função, infelizmente!)
aseagram
aseagram: simplesmente substitua "utf-8" por "iso-8859-1" e deve funcionar. Se você estiver no Windows, provavelmente deverá usar "cp1252".
MiniQuark 13/06
BTW, reload(sys); sys.setdefaultencoding("utf-8")é um hack duvidoso às vezes recomendado para sistemas Windows; consulte stackoverflow.com/questions/28657010/… para obter detalhes.
PM 2Ring
14

gensim.utils.deaccent (texto) de Gensim - modelagem de tópicos para humanos :

'Sef chomutovskych komunistu dostal postou bily prasek'

Outra solução é unidecode .

Observe que a solução sugerida com dados unicoded normalmente remove acentos apenas em alguns caracteres (por exemplo, se transforma 'ł'em '', e não em 'l').

Piotr Migdal
fonte
1
deaccentainda dá em łvez de l.
Lcieslak
Você não precisa instalar NumPye SciPyremover os acentos.
Nuno André
obrigado pela referência gensim! como ele se compara ao unidecode (em termos de velocidade ou precisão)?
Etienne Kintzler 20/12/19
3

Alguns idiomas combinam diacríticos como letras de idioma e diacríticos de acento para especificar o acento.

Eu acho que é mais seguro especificar explicitamente quais diátricas você deseja remover:

def strip_accents(string, accents=('COMBINING ACUTE ACCENT', 'COMBINING GRAVE ACCENT', 'COMBINING TILDE')):
    accents = set(map(unicodedata.lookup, accents))
    chars = [c for c in unicodedata.normalize('NFD', string) if c not in accents]
    return unicodedata.normalize('NFC', ''.join(chars))
sirex
fonte