Was ist der beste Weg, um Akzente in einer Python-Unicode-Zeichenfolge zu entfernen?

507

Ich habe eine Unicode-Zeichenfolge in Python und möchte alle Akzente (diakritische Zeichen) entfernen.

Ich habe im Web eine elegante Möglichkeit gefunden, dies in Java zu tun:

  1. Konvertieren Sie die Unicode-Zeichenfolge in ihre lange normalisierte Form (mit einem separaten Zeichen für Buchstaben und Diakritika).
  2. Entfernen Sie alle Zeichen, deren Unicode-Typ "diakritisch" ist.

Muss ich eine Bibliothek wie pyICU installieren oder ist dies nur mit der Python-Standardbibliothek möglich? Und was ist mit Python 3?

Wichtiger Hinweis: Ich möchte Code mit einer expliziten Zuordnung von Zeichen mit Akzent zu ihrem Gegenstück ohne Akzent vermeiden.

MiniQuark
quelle

Antworten:

447

Unidecode ist die richtige Antwort darauf. Es transkribiert jede Unicode-Zeichenfolge in die nächstmögliche Darstellung im ASCII-Text.

Beispiel:

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
quelle
67
Scheint gut mit Chinesisch zu funktionieren, aber die Umwandlung des französischen Namens "François" ergibt leider "FranASSois", was im Vergleich zu dem natürlicheren "Francois" nicht sehr gut ist.
Eric O Lebigot
10
hängt davon ab, was Sie erreichen wollen. zum Beispiel mache ich jetzt eine Suche, und ich will nicht , griechisch / russisch / chinesisch transkribieren, ich möchte nur ersetzen , „A / E / S / C“ mit „a / e / s / c“
Kolinko
58
@EOL Unidecode eignet sich hervorragend für Zeichenfolgen wie "François", wenn Sie Unicode-Objekte an ihn übergeben. Es sieht so aus, als hätten Sie es mit einer einfachen Byte-Zeichenfolge versucht.
Karl Bartel
26
Beachten Sie, dass Unidecode> = 0.04.10 (Dezember 2012) GPL ist. Verwenden Sie frühere Versionen oder überprüfen Sie github.com/kmike/text-unidecode, wenn Sie eine freizügigere Lizenz benötigen und eine etwas schlechtere Implementierung aushalten können.
Mikhail Korobov
10
unidecodeersetzt °durch deg. Es ist mehr als nur das Entfernen von Akzenten.
Eric Duminil
273

Wie wäre es damit:

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

Dies funktioniert auch bei griechischen Buchstaben:

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

Die Zeichenkategorie "Mn" steht für Nonspacing_Mark, ähnlich wie unicodedata.combining in der Antwort von MiniQuark (ich habe nicht an unicodedata.combining gedacht, aber es ist wahrscheinlich die bessere Lösung, weil es expliziter ist).

Beachten Sie jedoch, dass diese Manipulationen die Bedeutung des Textes erheblich verändern können. Akzente, Umlaute usw. sind keine "Dekoration".

oefe
quelle
6
Dies sind leider keine komponierten Zeichen - obwohl "ł" "LATIN SMALL LETTER L WITH STROKE" heißt! Sie müssen entweder Spiele mit Parsing spielen unicodedata.nameoder eine ähnliche Tabelle verwenden, die Sie ohnehin für griechische Buchstaben benötigen (Α ist nur "GREEK CAPITAL LETTER ALPHA").
Alexis
2
@andi, ich fürchte, ich kann nicht erraten, welchen Punkt du machen willst. Der E-Mail-Austausch spiegelt das wider, was ich oben geschrieben habe: Da der Buchstabe "ł" kein Akzentbuchstabe ist (und im Unicode-Standard nicht als solcher behandelt wird), weist er keine Zerlegung auf.
Alexis
2
@alexis (spätes Follow-up): Dies funktioniert auch perfekt für Griechisch - z. "GREEK CAPITAL LETTER ALPHA MIT DASIA UND VARIA" wird wie erwartet in "GREEK CAPITAL LETTER ALPHA" normalisiert. Es sei denn, Sie beziehen sich auf die Transliteration (z. B. "α" → "a"), die nicht mit "Entfernen von Akzenten" identisch ist ...
lenz
@lenz, ich habe nicht über das Entfernen von Akzenten aus dem Griechischen gesprochen, sondern über den "Strich" auf der Ell. Da es kein diakritisches Zeichen ist, ist das Ändern in einfaches Ell dasselbe wie das Ändern von griechischem Alpha in A. Wenn Sie es nicht wollen, tun Sie es nicht, aber in beiden Fällen ersetzen Sie ein lateinisches (fast) ähnliches Aussehen.
Alexis
Funktioniert meistens gut :) Aber es verwandelt ßsich ssim Beispiel nicht in ASCII . Ich würde immer noch verwenden unidecode, um Unfälle zu vermeiden.
Art
146

Ich habe gerade diese Antwort im Web gefunden:

import unicodedata

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

Es funktioniert gut (zum Beispiel für Französisch), aber ich denke, der zweite Schritt (Entfernen der Akzente) könnte besser gehandhabt werden als das Löschen der Nicht-ASCII-Zeichen, da dies für einige Sprachen (z. B. Griechisch) fehlschlägt. Die beste Lösung wäre wahrscheinlich, die Unicode-Zeichen, die als diakritisch gekennzeichnet sind, explizit zu entfernen.

Bearbeiten : das macht den Trick:

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)wird true zurückgeben, wenn das Zeichen cmit dem vorhergehenden Zeichen kombiniert werden kann, dh hauptsächlich, wenn es diakritisch ist.

Bearbeiten 2 : remove_accentsErwartet eine Unicode- Zeichenfolge, keine Byte-Zeichenfolge. Wenn Sie eine Byte-Zeichenfolge haben, müssen Sie diese in eine Unicode-Zeichenfolge wie folgt dekodieren:

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
quelle
5
Ich musste 'utf8' zu Unicode hinzufügen:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba
@Jabba: , 'utf8'ist ein "Sicherheitsnetz", das benötigt wird, wenn Sie Eingaben im Terminal testen (das standardmäßig keinen Unicode verwendet). Aber in der Regel Sie nicht haben , um es hinzuzufügen, denn wenn Sie Akzente sind zu entfernen dann input_strsehr wahrscheinlich ist bereits seine utf8. Es tut jedoch nicht weh, in Sicherheit zu sein.
MestreLion
1
@rbp: Sie sollten eine Unicode-Zeichenfolge remove_accentsanstelle einer regulären Zeichenfolge übergeben (u "é" anstelle von "é"). Sie haben eine reguläre Zeichenfolge an übergeben remove_accents. Beim Versuch, Ihre Zeichenfolge in eine Unicode-Zeichenfolge zu konvertieren, wurde die Standardcodierung asciiverwendet. Diese Codierung unterstützt kein Byte mit einem Wert> 127. Wenn Sie "é" in Ihre Shell eingegeben haben, hat Ihr Betriebssystem dies codiert, wahrscheinlich mit UTF-8 oder einer Windows-Codepage-Codierung, und dies beinhaltete Bytes> 127. Ich werde meine Funktion ändern, um die Konvertierung in Unicode zu entfernen: Sie wird deutlicher bombardiert, wenn eine Nicht-Unicode-Zeichenfolge übergeben wird.
MiniQuark
1
@MiniQuark, der perfekt funktionierte >>> remove_accents (Unicode ('é'))
rbp
1
Diese Antwort ergab das beste Ergebnis für einen großen Datensatz. Die einzige Ausnahme ist "ð" - Unicodedata würden es nicht berühren!
29.
43

Eigentlich arbeite ich an projektkompatiblem Python 2.6, 2.7 und 3.4 und muss IDs aus freien Benutzereinträgen erstellen.

Dank dir habe ich diese Funktion geschaffen, die Wunder wirkt.

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

Ergebnis:

text_to_id("Montréal, über, 12.89, Mère, Françoise, noël, 889")
>>> 'montreal_uber_1289_mere_francoise_noel_889'
hexaJer
quelle
2
Übergeben Sie mit Py2.7 einen bereits Unicode-String-Fehler an text = unicode(text, 'utf-8'). Eine Problemumgehung dafür war hinzuzufügenexcept TypeError: pass
Daniel Reis
Sehr laut! Hat in meinem Fall funktioniert. Uma seleção de poesia brasileira para desenvolver eine Kapazität de escuta dos alunos idioma Português.
Aaron
23

Dies behandelt nicht nur Akzente, sondern auch "Striche" (wie in ø usw.):

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

Dies ist die eleganteste Art, die ich mir vorstellen kann (und sie wurde von alexis in einem Kommentar auf dieser Seite erwähnt), obwohl ich sie in der Tat nicht für sehr elegant halte. Tatsächlich ist es eher ein Hack, wie in Kommentaren erwähnt, da Unicode-Namen - eigentlich nur Namen - keine Garantie dafür geben, dass sie konsistent sind oder so.

Es gibt immer noch spezielle Buchstaben, die hiervon nicht behandelt werden, z. B. umgedrehte und invertierte Buchstaben, da ihr Unicode-Name kein 'WITH' enthält. Es hängt davon ab, was Sie sowieso tun möchten. Ich brauchte manchmal Akzententfernung, um die Sortierreihenfolge des Wörterbuchs zu erreichen.

NOTIZ BEARBEITEN:

Enthaltene Vorschläge aus den Kommentaren (Behandlung von Suchfehlern, Python-3-Code).

lenz
quelle
8
Sie sollten die Ausnahme abfangen, wenn das neue Symbol nicht vorhanden ist. Zum Beispiel gibt es SQUARE WITH VERTICAL FILL ▥, aber es gibt kein SQUARE. (Ganz zu schweigen davon, dass dieser Code UMBRELLA WITH RAIN DROPS ☔ in UMBRELLA ☂ umwandelt).
Janek37
Dies sieht elegant aus, wenn die semantischen Beschreibungen der verfügbaren Zeichen genutzt werden. Brauchen wir dort wirklich den unicodeFunktionsaufruf mit Python 3? Ich denke, ein engerer regulärer Ausdruck anstelle von findwürde alle im obigen Kommentar erwähnten Probleme vermeiden, und Memoisierung würde die Leistung verbessern, wenn es sich um einen kritischen Codepfad handelt.
Matanster
1
@matanster nein, dies ist eine alte Antwort aus der Python-2-Ära; Der unicodeTypecast ist in Python 3 nicht mehr geeignet. Meiner Erfahrung nach gibt es keine universelle, elegante Lösung für dieses Problem. Je nach Anwendung hat jeder Ansatz seine Vor- und Nachteile. Qualitätsbewusste Werkzeuge wie unidecodebasieren auf handgefertigten Tischen. Einige Ressourcen (Tabellen, Algorithmen) werden von Unicode bereitgestellt, z. zur Zusammenstellung.
Lenz
1
Ich wiederhole nur, was oben steht (py3): 1) Unicode (char) -> char 2) Versuch: return ud.lookup (desc) außer KeyError: return char
mirek
@mirek Sie haben Recht: Da dieser Thread so beliebt ist, verdient diese Antwort eine Aktualisierung / Verbesserung. Ich habe es bearbeitet.
Lenz
15

Als Antwort auf die Antwort von @ MiniQuark:

Ich habe versucht, eine halbfranzösische CSV-Datei (mit Akzenten) und einige Zeichenfolgen einzulesen, die schließlich zu Ganzzahlen und Gleitkommazahlen werden. Als Test habe ich eine test.txtDatei erstellt, die folgendermaßen aussieht:

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

Ich musste Zeilen einfügen 2und 3es zum Laufen bringen (was ich in einem Python-Ticket gefunden habe) sowie @ Jabbas Kommentar einfügen:

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)

Das Ergebnis:

Montreal
uber
12.89
Mere
Francoise
noel
889

(Hinweis: Ich arbeite unter Mac OS X 10.8.4 und verwende Python 2.7.3.)

Aseagramm
quelle
1
remove_accentssollte Akzente aus einer Unicode-Zeichenfolge entfernen. Falls eine Byte-Zeichenfolge übergeben wird, wird versucht, diese mit in eine Unicode-Zeichenfolge zu konvertieren unicode(input_str). Dies verwendet die Standardcodierung von Python, "ascii". Da Ihre Datei mit UTF-8 codiert ist, schlägt dies fehl. In den Zeilen 2 und 3 wird die Standardcodierung von Python in UTF-8 geändert. Dann funktioniert es, wie Sie herausgefunden haben. Eine andere Möglichkeit besteht darin, remove_accentseine Unicode-Zeichenfolge zu übergeben: Entfernen Sie die Zeilen 2 und 3 und ersetzen Sie sie in der letzten Zeile elementdurch element.decode("utf-8"). Ich habe getestet: es funktioniert. Ich werde meine Antwort aktualisieren, um dies klarer zu machen.
MiniQuark
Schöne Bearbeitung, guter Punkt. (Noch ein Hinweis: Das eigentliche Problem, das ich festgestellt habe, ist, dass meine iso-8859-1
Datendatei
Aseagramm: Ersetzen Sie einfach "utf-8" durch "iso-8859-1", und es sollte funktionieren. Wenn Sie unter Windows arbeiten, sollten Sie stattdessen wahrscheinlich "cp1252" verwenden.
MiniQuark
Übrigens reload(sys); sys.setdefaultencoding("utf-8")ist ein zweifelhafter Hack, der manchmal für Windows-Systeme empfohlen wird. Weitere Informationen finden Sie unter stackoverflow.com/questions/28657010/… .
PM 2Ring
14

gensim.utils.deaccent (Text) von Gensim - Themenmodellierung für Menschen :

'Sef chomutovskych komunistu dostal postou bily prasek'

Eine andere Lösung ist Unidecode .

Beachten Sie, dass die vorgeschlagene Lösung mit Unicodedata normalerweise nur in bestimmten Zeichen Akzente entfernt (z. B. 'ł'in ''und nicht in 'l').

Piotr Migdal
quelle
1
deaccentgibt immer noch łstatt l.
Lcieslak
Sie müssen nicht installieren NumPyund SciPyAkzente entfernen lassen.
Nuno André
danke für gensim referenz! Wie ist der Vergleich mit Unidecode (in Bezug auf Geschwindigkeit oder Genauigkeit)?
Etienne Kintzler
3

In einigen Sprachen werden Diakritika als Sprachbuchstaben und Akzentdiakritika kombiniert, um den Akzent festzulegen.

Ich denke, es ist sicherer, explizit anzugeben, welche Diakrika Sie entfernen möchten:

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
quelle