Как лучше всего удалить акценты (нормализовать) в строке Unicode Python?
У меня есть строка в формате Unicode в Python, и я хотел бы удалить все диакритические знаки (акценты).
Я нашел элегантное решение этой задачи в Java:
- Конвертировать строку Unicode в её длинную нормализованную форму (с отдельными символами для букв и диакритиков).
- Удалить все символы, у которых тип Unicode — "диакритический".
Нужно ли мне устанавливать библиотеку, такую как pyICU, или это можно сделать только с помощью стандартной библиотеки Python? И как обстоят дела с Python 3?
Важно: я хотел бы избежать кода с явным сопоставлением акцентированных символов с их неакцентированными аналогами.
5 ответ(ов)
Вам нужно удалить акценты из строки? Приведенный код действительно работает, но, как вы отметили, он может быть не лучшим в сценариях с некоторыми языками, так как удаление не-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)
Таким образом, вы можете безопасно работать с текстами на разных языках, не боясь потерять важные символы.
Отвечая на ваш вопрос, вот функция, которая помогает создать идентификаторы из произвольных текстовых данных, обеспечивая совместимость с 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'
Таким образом, вы сможете безопасно преобразовывать строки с акцентами и другими символами в удобный формат идентификатора.
Это решение обрабатывает не только акценты, но и «дизизды» (например, ø и т.д.):
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).
На мой взгляд, предложенные решения не должны считаться верными. Исходный вопрос касается удаления акцентов, поэтому правильный ответ должен делать только это, а не вносить другие, неуточненные изменения.
Просто посмотрите на результат следующего кода, который является принятым ответом. Я поменял "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
Эта функция корректно удаляет акценты, не внося дополнительных изменений.
В ответ на ответ @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)
UnicodeEncodeError: 'ascii' кодек не может закодировать символ u'\xa0' на позиции 20: номер не в диапазоне (128)
UnicodeDecodeError: Кодек 'charmap' не может декодировать байт X в позиции Y: символ отображается как <неопределённый>
Фиксация количества знаков после запятой с помощью f-строк
Ошибка: "'dict' объект не имеет метода 'iteritems'"
Ошибка UnicodeDecodeError при чтении CSV-файла в Pandas