Jaki jest najlepszy sposób na usunięcie akcentów (normalizację) w łańcuchu Unicode Pythona?

Mam ciąg Unicode w Pythonie i chciałbym usunąć wszystkie akcenty (znaki diakrytyczne).

Znalazłem w sieci elegancki sposób na to (w Javie):

  1. Konwertuj łańcuch Unicode na jego długo znormalizowana forma (z oddzielnym znakiem dla liter i znaków diakrytycznych)
  2. usuń wszystkie znaki, których typem Unicode jest "diacritic".

Czy muszę instalować bibliotekę taką jak pyICU czy jest to możliwe tylko za pomocą Python standard library? A co z Pythonem 3?

Ważna uwaga: chciałbym uniknąć kodu z wyraźnym mapowaniem ze znaków akcentowanych do ich nieakcentowanego odpowiednika.

Author: smci, 2009-02-05

9 answers

Unidecode jest poprawną odpowiedzią na to pytanie. Transliteracja dowolnego ciągu unicode na najbliższą możliwą reprezentację w tekście ascii.

Przykład:

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'
 527
Author: Christian Oudard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-21 17:23:40

A może tak:

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

To działa również na litery greckie:

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

Kategoria znaków "Mn" oznacza Nonspacing_Mark, która jest podobna do unicodedata.łączenie w odpowiedzi Miniquarka (nie myślałem o unicodedata.łączenie, ale to chyba lepsze rozwiązanie, bo jest bardziej wyraziste).

I należy pamiętać, że te manipulacje mogą znacząco zmienić znaczenie tekstu. Akcenty, Umlauty itp. nie są "dekoracją".

 303
Author: oefe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-28 13:39:51

Właśnie znalazłem taką odpowiedź w sieci:

import unicodedata

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

To działa dobrze (dla francuskiego, na przykład), ale myślę, że drugi krok (usunięcie akcentów) może być obsługiwany lepiej niż opuszczenie znaków nie-ASCII, ponieważ to nie będzie w niektórych językach(Grecki, na przykład). Najlepszym rozwiązaniem byłoby prawdopodobnie jawne usunięcie znaków unicode, które są oznaczone jako diakrytyczne.

Edit : this does the 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) zwróci true jeśli znak c może być łączony z poprzedzającym znakiem, to jest głównie, jeśli jest to znak diakrytyczny.

Edytuj 2: remove_accents oczekuje ciągu unicode, a nie ciągu bajtów. Jeśli masz łańcuch bajtowy, musisz go zdekodować do ciągu unicode, takiego jak:

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)
 165
Author: MiniQuark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-14 17:01:00

Właściwie pracuję na Pythonie 2.6, 2.7 i 3.4 zgodnym z projektem i muszę tworzyć ID z wolnych wpisów użytkowników.

Dzięki tobie stworzyłem tę funkcję, która czyni cuda.
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

Wynik:

text_to_id("Montréal, über, 12.89, Mère, Françoise, noël, 889")
>>> 'montreal_uber_1289_mere_francoise_noel_889'
 50
Author: hexaJer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-23 22:12:14

To obsługuje nie tylko akcenty, ale także "pociągnięcia" (jak w ø itp.):

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

To najbardziej elegancki sposób, jaki mogę wymyślić (i został wspomniany przez alexis w komentarzu na tej stronie), chociaż nie sądzę, że jest bardzo elegancki. W rzeczywistości, to jest bardziej hack, jak wskazano w komentarzach, ponieważ nazwy Unicode są-tak naprawdę tylko nazwy, nie dają gwarancji, aby być spójne lub cokolwiek.

Są jeszcze specjalne litery, które nie są obsługiwane przez to, takie jak odwrócone i odwrócone litery, ponieważ ich nazwa unicode nie zawiera 'WITH'. To zależy od tego, co chcesz robić. Czasami potrzebowałem usuwania akcentu, aby osiągnąć kolejność sortowania słownika.

EDIT NOTE:

Włączone sugestie z komentarzy(obsługa błędów wyszukiwania, Kod Pythona-3).

 27
Author: lenz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-08 18:19:51

W odpowiedzi na odpowiedź @ MiniQuark:

Próbowałem przeczytać w pliku csv, który był półfrancuski (zawierający akcenty), a także kilka ciągów, które ostatecznie staną się liczbami całkowitymi i pływakami. Jako test stworzyłem plik test.txt, który wyglądał tak:

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

Musiałem dodać linie 2 i 3, Aby to zadziałało( co znalazłem w bilecie Pythona), a także włączyć @ Jabba ' s komentarz:

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)

Wynik:

Montreal
uber
12.89
Mere
Francoise
noel
889

(Uwaga: jestem na Mac OS X 10.8.4 i używam Pythona 2.7.3)

 15
Author: aseagram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-12 18:10:05

Gensim.utils.deaccent (text) from Gensim-topic modelling for humans :

'Sef chomutovskych komunistu dostal postou bily prasek'

Innym rozwiązaniem jest unidecode .

Należy zauważyć, że sugerowane rozwiązanie z unicodedata zazwyczaj usuwa akcenty tylko w niektórych znakach (np. zamienia 'ł' na '', a nie na 'l').

 14
Author: Piotr Migdal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-16 04:23:00

Niektóre języki mają znaki diakrytyczne łączące jako litery językowe i znaki diakrytyczne akcentu, aby określić akcent.

Myślę, że bezpieczniej jest wyraźnie określić, jakie znaki diakrytyczne chcesz usunąć:

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))
 4
Author: sirex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-24 11:34:02

perfplot

import unicodedata
from random import choice

import perfplot
import regex
import text_unidecode


def remove_accent_chars_regex(x: str):
    return regex.sub(r'\p{Mn}', '', unicodedata.normalize('NFKD', x))


def remove_accent_chars_join(x: str):
    # answer by MiniQuark
    # https://stackoverflow.com/a/517974/7966259
    return u"".join([c for c in unicodedata.normalize('NFKD', x) if not unicodedata.combining(c)])


perfplot.show(
    setup=lambda n: ''.join([choice('Málaga François Phút Hơn 中文') for i in range(n)]),
    kernels=[
        remove_accent_chars_regex,
        remove_accent_chars_join,
        text_unidecode.unidecode,
    ],
    labels=['regex', 'join', 'unidecode'],
    n_range=[2 ** k for k in range(22)],
    equality_check=None, relative_to=0, xlabel='str len'
)
 2
Author: mo-han,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-02-03 02:59:45