Який найкращий спосіб видалити наголоси в рядку Unicode Python?


506

У мене в Python є рядок Unicode, і я хотів би видалити всі наголоси (діакритики).

Я знайшов в Інтернеті елегантний спосіб зробити це на Java:

  1. перетворити рядок Unicode у його довгу нормовану форму (з окремим символом для літер та діакритики)
  2. видаліть усі символи, тип Unicode - "діакритичний".

Чи потрібно мені встановити таку бібліотеку, як pyICU, чи це можливо лише за допомогою стандартної бібліотеки python? А як щодо python 3?

Важлива примітка: Я хотів би уникати коду з чітким відображенням від наголошених символів до їх аналога без акценту.

Відповіді:


447

Unidecode - правильна відповідь на це. Це транслітералізує будь-яку рядок unicode в найближче можливе подання в тексті ascii.

Приклад:

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
Здається, добре співпрацює з китайською, але трансформація французької назви "Франсуа", на жаль, дає "FranASSois", що не дуже добре, порівняно з більш природним "Франсуа".
Eric O Lebigot

10
залежить від того, що ви намагаєтеся досягти. наприклад, я зараз роблю пошук, і я не хочу перекладати грецьку / російську / китайську, я просто хочу замінити "ą / ę / ś / ć" на "a / e / s / c"
kolinko

58
@EOL unidecode відмінно підходить для рядків типу "François", якщо ви передаєте йому об'єкти unicode. Схоже, ви намагалися зі звичайним байтовим рядком.
Карл Бартель

26
Зауважте, що unidecode> = 0,04.10 (грудень 2012) є GPL. Використовуйте більш ранні версії або перевірте github.com/kmike/text-unidecode, якщо вам потрібна більш дозвільна ліцензія і ви можете трохи погіршити реалізацію.
Михайло Коробов

10
unidecodeзамінює °на deg. Це більше, ніж просто видалення акцентів.
Ерік Думініл

273

Як щодо цього:

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

Це працює і на грецьких літерах:

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

Категорія символів «Mn» означає Nonspacing_Mark, що аналогічно unicodedata.combining у відповідь MiniQuark в (я не думаю про unicodedata.combining, але це, ймовірно, краще рішення, тому що це більш явним).

І майте на увазі, ці маніпуляції можуть суттєво змінити зміст тексту. Акценти, Umlauts тощо не є "прикрасою".


6
На жаль, це не складені символи - незважаючи на те, що "ł" названо "LATIN SMALL LETTER L With STROKE"! Вам або потрібно буде грати в ігри з розбором unicodedata.name, або розбиватись та використовувати таблицю, схожу на вигляд, - яка вам так чи інакше знадобиться для грецьких літер (Α це просто "ВЕЛИКА КАПІТАЛЬНА ЛІТАРНА АЛЬФА").
alexis

2
@andi, я боюся, я не можу здогадатися, який пункт ти хочеш зробити. Обмін електронною поштою відображає те, що я писав вище: Оскільки літера "ł" не є буквою з наголосом (і не розглядається як одна у стандарті Unicode), вона не має розкладання.
alexis

2
@ Алексис (пізнє спостереження): Це прекрасно працює і для грецької мови - наприклад. "ГРЕКСЬКИЙ КАПІТАЛ ЛІТИНИ АЛЬФА З ДАСІЄЮ ТА ВАРІЯМ" нормалізується на "ГРЕКСЬКИЙ КАПІТАЛЬНИЙ ЛИСТ АЛЬФА" так само, як очікувалося. Якщо ви не маєте на увазі транслітерацію (напр., "Α" → "a"), що не те саме, що "видалення наголосів" ...
lenz

@lenz, я говорив не про видалення наголосів з грецької мови, а про "штрих" на ell. Оскільки це не діакритично, змінити його на звичайний ell - це те саме, що змінити грецьку альфа на A. Якщо ви не хочете, не робіть цього, але в обох випадках ви замінюєте латинську (близьку) схожу.
alexis

Переважно працює добре :) Але це, наприклад, не перетворюється ßна ascii ss. Я б все-таки використовував, unidecodeщоб уникнути аварій.
Арт

146

Я щойно знайшов цю відповідь в Інтернеті:

import unicodedata

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

Це добре працює (наприклад, для французької), але я думаю, що з другим кроком (видалення наголосів) можна було б впоратися краще, ніж відкинути символи, що не належать до ASCII, тому що це стане невдалим для деяких мов (наприклад, грецької). Найкращим рішенням, мабуть, було б явно видалити символи Unicode, які позначені як діакритичні.

Редагувати : це робить фокус:

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)повернеться істиною, якщо персонаж cможна поєднати з попереднім символом, тобто головним чином, якщо це діакритичний знак.

Редагувати 2 : remove_accentsочікує рядок Unicode , а не байт. Якщо у вас є рядок байтів, ви повинні розшифрувати його в рядок Unicode, як це:

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
Мені довелося додати "utf8" до unicode:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba

@Jabba: , 'utf8'потрібна "мережа безпеки", якщо ви тестуєте вхід в терміналі (який за замовчуванням не використовує unicode). Але зазвичай не потрібно його додавати, оскільки якщо ви видаляєте акценти, то input_str, швидше за все, це вже буде utf8. Але це не завадить бути безпечним.
MestreLion

1
@rbp: слід передати рядок Unicode remove_accentsзамість звичайної рядки (u "é" замість "é"). Ви передали звичайну рядок до remove_accents, тому при спробі перетворення рядка в рядок unicode asciiбуло використано кодування за замовчуванням . Це кодування не підтримує жоден байт, значення якого> 127. Коли ви ввели оболонку "é" в оболонці, ваша ОС кодувала це, ймовірно, з кодуванням UTF-8 або деяким кодуванням сторінки кодової сторінки Windows, і що включало байти> 127. Я зміню свою функцію для того, щоб видалити перетворення в unicode: воно буде більш чітко, якщо буде передано рядок без унікоду.
MiniQuark

1
@MiniQuark, який прекрасно працював >>> Remove_accents (unicode ('é'))
rbp

1
Ця відповідь дала найкращий результат для великого набору даних, єдиний виняток - "ð" - unicodedata не торкнеться цього!
s29

43

Насправді я працюю над сумісними проектами python 2.6, 2.7 та 3.4, і мені потрібно створити ідентифікатори з безкоштовних записів користувачів.

Завдяки вам я створив цю функцію, яка творить чудеса.

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

результат:

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

2
З Py2.7, передаючи вже Unicode рядкові помилки в text = unicode(text, 'utf-8'). Для вирішення цього було додатиexcept TypeError: pass
Даніель Рейс

Дуже чутно! Працював у моєму випадку. Uma seleção de poesia brasileira para desenvolver a capacidade de escuta dos alunos idioma Português.
Аарон

23

Це обробляє не тільки наголоси, але й "штрихи" (як у ø тощо):

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

Це найелегантніший спосіб, про який я можу подумати (і про це Алексис згадував у коментарі на цій сторінці), хоча я не думаю, що це дуже елегантно. Насправді, це більше хак, як вказувалося в коментарях, оскільки імена Unicode - це справді просто імена, вони не дають жодних гарантій бути послідовними чи нічого.

Є ще спеціальні букви, які цим не обробляються, наприклад, перевернуті та перевернуті літери, оскільки їхнє ім'я Unicode не містить "З". Це залежить від того, що ви хочете все-таки зробити. Мені іноді потрібна була наголос наголосу для досягнення порядку сортування словника.

РЕДАКТУВАТИ ПРИМІТКА:

Включені пропозиції з коментарів (обробка помилок пошуку, код Python-3).


8
Ви повинні зловити виняток, якщо нового символу не існує. Наприклад, МІСЦЕ З ВЕРТИКАЛЬНИМИ ЗАЛОЗАМИ ▥, але НЕ МАЄТЬ. (не кажучи вже про те, що цей код перетворює UMBRELLA з дощовими краплями ☔ в UMBRELLA ☂).
janek37

Це виглядає елегантно, використовуючи наявні смислові описи персонажів. Нам дійсно потрібен unicodeфункціональний виклик там із python 3? Я думаю, що більш жорсткий регулярний вираз замість цього findдозволить уникнути всіх проблем, згаданих у коментарі вище, а також запам'ятовування допоможе виконувати продуктивність, коли це критичний шлях коду.
matanster

1
@matanster ні, це стара відповідь епохи Python-2; на unicodePython 3. друкарська клавіша більше не підходить. У будь-якому випадку, на мій досвід, немає універсального, елегантного рішення цієї проблеми. Залежно від програми будь-який підхід має свої плюси і мінуси. Якісні процвітаючі засоби unidecodeзасновані на виготовлених вручну таблицях. Деякі ресурси (таблиці, алгоритми) надаються Unicode, наприклад. для співставлення
lenz

1
Я просто повторюю, що вище (py3): 1) unicode (char) -> char 2) спробуйте: повернути ud.lookup (desc) за винятком KeyError: return char
mirek

@mirek ви праві: оскільки ця тема настільки популярна, ця відповідь заслуговує на деяке оновлення / покращення. Я це відредагував.
lenz

15

У відповідь на відповідь @ MiniQuark:

Я намагався прочитати у файлі csv, який був напівфранцузьким (містить акценти), а також деякі рядки, які з часом стануть цілими чи плаваючими. Як тест, я створив test.txtфайл, який виглядав приблизно так:

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

Мені довелося включити рядки 2та 3примусити його працювати (що я знайшов у квитку python), а також включити коментар @ 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)

Результат:

Montreal
uber
12.89
Mere
Francoise
noel
889

(Примітка. Я перебуваю на Mac OS X 10.8.4 і використовую Python 2.7.3)


1
remove_accentsпризначався для видалення наголосів з рядка unicode. У випадку, якщо він пройшов байт-рядок, він намагається перетворити його в рядок unicode unicode(input_str). Для цього використовується кодування за замовчуванням python, яке є "ascii". Оскільки ваш файл кодується UTF-8, це не вдасться. Рядки 2 і 3 змінюють кодування за замовчуванням python на UTF-8, тож воно працює, як ви з’ясували. Інший варіант - передавати remove_accentsрядок unicode: видаліть рядки 2 і 3, а в останньому рядку замініть elementна element.decode("utf-8"). Я перевірив: це працює. Я оновлю свою відповідь, щоб зробити це зрозумілішим.
MiniQuark

Приємна редакція, хороший пункт. (Ще одна примітка: Справжня проблема, яку я зрозумів, полягає в тому, що мій файл даних, мабуть, закодований iso-8859-1, і я, на жаль, не можу
приступити

асеаграма: просто замініть "utf-8" на "iso-8859-1", і це повинно працювати. Якщо ви знаходитесь у Windows, то, ймовірно, замість цього ви повинні використовувати "cp1252"
MiniQuark

BTW reload(sys); sys.setdefaultencoding("utf-8")- це сумнівний злом, який іноді рекомендується використовувати для систем Windows; детальніше див. stackoverflow.com/questions/28657010/… .
PM 2Ring

14

gensim.utils.deaccent (текст) від Gensim - тематичне моделювання для людей :

'Sef chomutovskych komunistu dostal postou bily prasek'

Ще одне рішення - unidecode .

Зауважте, що запропоноване рішення з unicodedata, як правило, видаляє наголоси лише в якомусь символі (наприклад, перетворюється 'ł'на '', а не на 'l').


1
deaccentвсе ще дає łзамість l.
lcieslak

Вам не потрібно встановлювати NumPyта SciPyвидаляти акценти.
Нуно Андре

дякую за довідку gensim! як воно порівнюється з unidecode (за швидкістю чи точністю)?
Етьєн Кінцлер

3

Деякі мови поєднують діакритику як букву мови та діакритику наголосу, щоб вказати наголос.

Я думаю, що більш безпечно чітко вказати, які діалектрики ви бажаєте зняти:

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))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.