8

Как лучше всего удалить акценты (нормализовать) в строке Unicode Python?

1

У меня есть строка в формате Unicode в Python, и я хотел бы удалить все диакритические знаки (акценты).

Я нашел элегантное решение этой задачи в Java:

  1. Конвертировать строку Unicode в её длинную нормализованную форму (с отдельными символами для букв и диакритиков).
  2. Удалить все символы, у которых тип Unicode — "диакритический".

Нужно ли мне устанавливать библиотеку, такую как pyICU, или это можно сделать только с помощью стандартной библиотеки Python? И как обстоят дела с Python 3?

Важно: я хотел бы избежать кода с явным сопоставлением акцентированных символов с их неакцентированными аналогами.

5 ответ(ов)

2

Вам нужно удалить акценты из строки? Приведенный код действительно работает, но, как вы отметили, он может быть не лучшим в сценариях с некоторыми языками, так как удаление не-ASCII символов может привести к потере информации. Вот более оптимальное решение:

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"  # или iso-8859-15, или cp1252, в зависимости от используемой кодировки
byte_string = b"café"  # или просто "café" до Python 3.
unicode_string = byte_string.decode(encoding)

Таким образом, вы можете безопасно работать с текстами на разных языках, не боясь потерять важные символы.

0

Отвечая на ваш вопрос, вот функция, которая помогает создать идентификаторы из произвольных текстовых данных, обеспечивая совместимость с Python 2.6, 2.7 и 3.4.

Я разработал две функции: strip_accents, которая удаляет акценты из строки, и text_to_id, которая преобразует входной текст в формат идентификатора. Привожу код ниже:

import re
import unicodedata

def strip_accents(text):
    """
    Удаляет акценты из входной строки.

    :param text: Входная строка.
    :type text: строка.

    :returns: Обработанная строка.
    :rtype: строка.
    """
    try:
        text = unicode(text, 'utf-8')  # Для Python 2
    except (TypeError, NameError):  # unicode по умолчанию в 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):
    """
    Преобразует входной текст в идентификатор.

    :param text: Входная строка.
    :type text: строка.

    :returns: Обработанная строка.
    :rtype: строка.
    """
    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'

Таким образом, вы сможете безопасно преобразовывать строки с акцентами и другими символами в удобный формат идентификатора.

0

Это решение обрабатывает не только акценты, но и «дизизды» (например, ø и т.д.):

import unicodedata as ud

def rmdiacritics(char):
    '''
    Возвращает базовый символ для заданного символа, "удаляя" любые
    диакритические знаки, такие как акценты или завитки и черты и т.п.
    '''
    desc = ud.name(char)
    cutoff = desc.find(' WITH ')
    if cutoff != -1:
        desc = desc[:cutoff]
        try:
            char = ud.lookup(desc)
        except KeyError:
            pass  # удаление "WITH ..." привело к недопустимому имени
    return char

Это, на мой взгляд, самый элегантный способ (как упоминал alexis в комментарии к этой странице), хотя я не думаю, что он действительно элегантен. На самом деле, это скорее хак, как указано в комментариях, поскольку имена символов Unicode — это по сути просто названия, которые не гарантируют последовательности или чего-то подобного.

Существуют также специальные буквы, которые не обрабатываются данным решением, например, перевернутые и инверсированные буквы, так как их имя в Unicode не содержит 'WITH'. В любом случае это зависит от ваших целей. Иногда мне нужно было удалить акценты для достижения сортировки в алфавитном порядке.

ЗАМЕТКА РЕДАКТОРА:

Включены предложения из комментариев (обработка ошибок поиска, код для Python 3).

0

На мой взгляд, предложенные решения не должны считаться верными. Исходный вопрос касается удаления акцентов, поэтому правильный ответ должен делать только это, а не вносить другие, неуточненные изменения.

Просто посмотрите на результат следующего кода, который является принятым ответом. Я поменял "Málaga" на "Málagueña":

accented_string = u'Málagueña'
# accented_string имеет тип 'unicode'
import unidecode
unaccented_string = unidecode.unidecode(accented_string)
# unaccented_string содержит 'Malaguena' и имеет тип 'str'

Здесь происходит дополнительное изменение (ñ → n), которое не было запрошено в исходном вопросе.

Вот простая функция, которая выполняет запрашиваемую задачу в нижнем регистре:

def f_remove_accents(old):
    """
    Удаляет обычные акцентированные символы, в нижнем регистре.
    Использует: regex.
    """
    new = old.lower()
    new = re.sub(r'[àáâãäå]', 'a', new)
    new = re.sub(r'[èéêë]', 'e', new)
    new = re.sub(r'[ìíîï]', 'i', new)
    new = re.sub(r'[òóôõö]', 'o', new)
    new = re.sub(r'[ùúûü]', 'u', new)
    return new

Эта функция корректно удаляет акценты, не внося дополнительных изменений.

0

В ответ на ответ @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)

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь