Qual è il modo migliore per rimuovere gli accenti in una stringa Unicode Python?


507

Ho una stringa Unicode in Python e vorrei rimuovere tutti gli accenti (segni diacritici).

Ho trovato sul Web un modo elegante per farlo in Java:

  1. converti la stringa Unicode nella sua forma lunga normalizzata (con un carattere separato per lettere e segni diacritici)
  2. rimuove tutti i caratteri il cui tipo Unicode è "diacritico".

Devo installare una libreria come pyICU o è possibile solo con la libreria standard di Python? E che dire di Python 3?

Nota importante: vorrei evitare il codice con una mappatura esplicita dai caratteri accentati alla loro controparte non accentata.

Risposte:


448

Unidecode è la risposta corretta per questo. Traslittera qualsiasi stringa unicode nella rappresentazione più vicina possibile nel testo ASCII.

Esempio:

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'

67
Sembra funzionare bene con il cinese, ma la trasformazione del nome francese "François" sfortunatamente dà "FranASSois", che non è molto buono, rispetto al più naturale "Francois".
Eric O Lebigot,

10
dipende da cosa stai cercando di ottenere. ad esempio sto facendo una ricerca in questo momento, e non voglio traslitterare greco / russo / cinese, voglio solo sostituire "ą / ę / ś / ć" con "a / e / s / c"
kolinko

58
@EOL unidecode funziona alla grande per stringhe come "François", se gli passi oggetti Unicode. Sembra che tu abbia provato con una semplice stringa di byte.
Karl Bartel,

26
Si noti che unidecode> = 0.04.10 (dic 2012) è GPL. Utilizza le versioni precedenti o controlla github.com/kmike/text-unidecode se hai bisogno di una licenza più permissiva e può sopportare un'implementazione leggermente peggiore.
Mikhail Korobov,

10
unidecodesostituisce °con deg. Fa molto di più che rimuovere semplicemente gli accenti.
Eric Duminil,

274

Cosa ne pensi di questo:

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

Funziona anche con lettere greche:

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

La categoria di caratteri "Mn" rappresenta Nonspacing_Mark, che è simile a unicodedata.combining nella risposta di MiniQuark (non ho pensato al unicodedata.combining, ma è probabilmente la soluzione migliore, perché è più esplicita).

E tieni presente che queste manipolazioni possono alterare in modo significativo il significato del testo. Accenti, Umlaut ecc. Non sono "decorazioni".


6
Purtroppo questi non sono personaggi composti, anche se "ł" è chiamato "LATINA PICCOLA LETTERA CON CORSA"! Dovrai giocare con l'analisi unicodedata.nameo abbattere e utilizzare un tavolo simile, che ti servirà comunque per le lettere greche (Α è solo "GREEK CAPITAL LETTER ALPHA").
alexis,

2
@andi, temo di non riuscire a indovinare quale punto vuoi sollevare. Lo scambio di e-mail riflette ciò che ho scritto sopra: poiché la lettera "ł" non è una lettera accentata (e non è trattata come una nello standard Unicode), non ha una scomposizione.
alexis,

2
@alexis (follow-up posticipato): funziona perfettamente anche per il greco, ad es. "LETTERA DI CAPITALE GRECA ALPHA CON DASIA E VARIA" è normalizzata in "LETTERA DI CAPITALE GRECA ALPHA" proprio come previsto. A meno che non ti riferisca alla traslitterazione (ad es. "Α" → "a"), che non equivale a "rimuovere gli accenti" ...
lenz,

@lenz, non stavo parlando di rimuovere gli accenti dal greco, ma del "colpo" sull'ell. Dal momento che non è un diacritico, cambiarlo in semplice ell è lo stesso che cambiare in Alpha greco A. Se non lo vuoi, non farlo, ma in entrambi i casi stai sostituendo un sosia latino (quasi).
alexis,

Principalmente funziona bene :) Ma non si trasforma ßin ascii ssin esempio. Userei ancora unidecodeper evitare incidenti.
Art

146

Ho appena trovato questa risposta sul 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

Funziona bene (per il francese, per esempio), ma penso che il secondo passo (rimuovere gli accenti) potrebbe essere gestito meglio che eliminare i caratteri non ASCII, perché questo fallirà per alcune lingue (greco, per esempio). La soluzione migliore sarebbe probabilmente quella di rimuovere esplicitamente i caratteri unicode che sono etichettati come segni diacritici.

Modifica : questo fa il trucco:

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)tornerà vero se il personaggio cpuò essere combinato con il carattere precedente, questo è principalmente se è un carattere diacritico.

Modifica 2 : si remove_accentsaspetta una stringa unicode , non una stringa di byte. Se hai una stringa di byte, devi decodificarla in una stringa unicode come questa:

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)

5
Ho dovuto aggiungere 'utf8' all'unicode:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba

@Jabba: , 'utf8'è una "rete di sicurezza" necessaria se si sta testando l'input nel terminale (che per impostazione predefinita non utilizza unicode). Ma di solito non è necessario aggiungerlo, poiché se si rimuovono gli accenti input_strè molto probabile che sia già utf8. Tuttavia, non è male essere al sicuro.
MestreLion,

1
@rbp: dovresti passare una stringa unicode remove_accentsinvece di una stringa normale (u "é" invece di "é"). Hai passato una stringa normale a remove_accents, quindi quando hai provato a convertire la stringa in una stringa unicode, è asciistata utilizzata la codifica predefinita . Questa codifica non supporta alcun byte il cui valore è> 127. Quando hai digitato "é" nella tua shell, il tuo sistema operativo lo ha codificato, probabilmente con la codifica UTF-8 o qualche codepage di Windows e che includeva byte> 127. Cambierò la mia funzione per rimuovere la conversione in unicode: bombarderà più chiaramente se viene passata una stringa non unicode.
MiniQuark,

1
@MiniQuark che ha funzionato perfettamente >>> remove_accents (unicode ('é'))
rbp

1
Questa risposta mi ha dato il miglior risultato su un set di dati di grandi dimensioni, l'unica eccezione è "ð" - unicodedata non lo toccherebbe!
s29,

43

Attualmente lavoro su progetti compatibili con Python 2.6, 2.7 e 3.4 e devo creare ID da voci utente gratuite.

Grazie a te, ho creato questa funzione che fa miracoli.

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

risultato:

text_to_id("Montréal, über, 12.89, Mère, Françoise, noël, 889")
>>> 'montreal_uber_1289_mere_francoise_noel_889'

2
Con Py2.7, passando errori di stringa già unicode in text = unicode(text, 'utf-8'). Una soluzione alternativa era quella di aggiungereexcept TypeError: pass
Daniel Reis, il

Molto rumoroso! Ha funzionato nel mio caso. Questa è la selezione di poesia brasiliana per la creazione di una capacità di fuga di idiomi portoghesi.
Aaron,

23

Questo gestisce non solo gli accenti, ma anche i "tratti" (come in ø ecc.):

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

Questo è il modo più elegante che mi viene in mente (ed è stato menzionato da Alexis in un commento in questa pagina), anche se non penso che sia davvero molto elegante. In effetti, è più un trucco, come sottolineato nei commenti, poiché i nomi Unicode sono - in realtà solo nomi, non danno alcuna garanzia di essere coerenti o altro.

Ci sono ancora lettere speciali che non sono gestite da questo, come lettere girate e invertite, poiché il loro nome unicode non contiene 'WITH'. Dipende da cosa vuoi fare comunque. A volte avevo bisogno di eliminare l'accento per ottenere l'ordinamento del dizionario.

MODIFICA NOTA:

Suggerimenti incorporati nei commenti (gestione degli errori di ricerca, codice Python-3).


8
Dovresti prendere l'eccezione se il nuovo simbolo non esiste. Ad esempio c'è SQUARE CON VERTICAL FILL ▥, ma non c'è SQUARE. (per non parlare del fatto che questo codice trasforma l'OMBRELLO CON GOCCE DI PIOGGIA ☔ in OMBRELLO ☂).
janek37,

Questo sembra elegante nel sfruttare le descrizioni semantiche dei personaggi disponibili. Abbiamo davvero bisogno della unicodechiamata di funzione lì dentro con Python 3? Penso che una regex più stretta al posto di findevitare tutti i problemi menzionati nel commento sopra, e anche la memoizzazione aiuterebbe le prestazioni quando si tratta di un percorso di codice critico.
matanster

1
@matanster no, questa è una vecchia risposta dell'era Python-2; il unicodetypecast non è più appropriato in Python 3. In ogni caso, nella mia esperienza non esiste una soluzione universale ed elegante a questo problema. A seconda dell'applicazione, ogni approccio ha i suoi pro e contro. Strumenti di qualità come unidecodequelli basati su tavoli artigianali. Alcune risorse (tabelle, algoritmi) sono fornite da Unicode, ad es. per la collazione.
lenz,

1
Ripeto, quanto sopra (py3): 1) unicode (char) -> char 2) provare: return ud.lookup (desc) tranne KeyError: return char
mirek

@mirek hai ragione: poiché questo thread è così popolare, questa risposta merita qualche aggiornamento / miglioramento. L'ho modificato.
lenz

15

In risposta alla risposta di @ MiniQuark:

Stavo cercando di leggere in un file CSV mezzo francese (contenente accenti) e anche alcune stringhe che alla fine sarebbero diventate numeri interi e float. Come test, ho creato un test.txtfile simile al seguente:

Montréal, über, 12.89, Mère, Françoise, noël, 889

Ho dovuto includere linee 2e 3farlo funzionare (che ho trovato in un ticket Python), oltre a incorporare il commento di @ 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)

Il risultato:

Montreal
uber
12.89
Mere
Francoise
noel
889

(Nota: sono su Mac OS X 10.8.4 e utilizzo Python 2.7.3)


1
remove_accentsera pensato per rimuovere gli accenti da una stringa unicode. Nel caso in cui abbia passato una stringa di byte, prova a convertirla in una stringa unicode con unicode(input_str). Questo utilizza la codifica predefinita di Python, che è "ascii". Dal momento che il tuo file è codificato con UTF-8, questo fallirebbe. Le righe 2 e 3 cambiano la codifica predefinita di Python in UTF-8, quindi funziona, come hai scoperto. Un'altra opzione è passare remove_accentsuna stringa unicode: rimuovere le righe 2 e 3 e sull'ultima riga sostituire elementcon element.decode("utf-8"). Ho provato: funziona. Aggiornerò la mia risposta per renderlo più chiaro.
MiniQuark,

Bella modifica, buon punto. (In un'altra nota: il vero problema che ho capito è che il mio file di dati è apparentemente codificato iso-8859-1, che purtroppo non riesco a lavorare con questa funzione!)
aseagram

aseagram: sostituisci semplicemente "utf-8" con "iso-8859-1" e dovrebbe funzionare. Se sei su Windows, probabilmente dovresti usare invece "cp1252".
MiniQuark,

A proposito, reload(sys); sys.setdefaultencoding("utf-8")è un trucco discutibile a volte raccomandato per i sistemi Windows; vedi stackoverflow.com/questions/28657010/… per i dettagli.
PM 2Ring

14

gensim.utils.deaccent (testo) di Gensim - modellazione di argomenti per l'uomo :

'Sef chomutovskych komunistu dostal postou bily prasek'

Un'altra soluzione è unidecode .

Si noti che la soluzione suggerita con unicodedata in genere rimuove gli accenti solo in alcuni caratteri (ad esempio si trasforma 'ł'in '', piuttosto che in 'l').


1
deaccentdà ancora łinvece di l.
Lcieslak,

Non è necessario installare NumPye SciPyrimuovere gli accenti.
Nuno André,

grazie per riferimento gensim! come si confronta con unidecode (in termini di velocità o precisione)?
Etienne Kintzler,

3

Alcune lingue hanno combinato segni diacritici come lettere linguistiche e segni diacritici di accento per specificare l'accento.

Penso che sia più sicuro specificare esplicitamente quali segni diatrici vuoi eliminare:

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))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.