Site icon 8HOST.COM

Как работать с Unicode в Python

Unicode (Юникод)  — это стандарт кодирования символов для большинства компьютеров. Он гарантирует, что текст — включая буквы, символы, эмодзи и даже управляющие символы — будет выглядеть одинаково на разных устройствах, платформах и в цифровых документах, независимо от операционной системы или программного обеспечения. Это важная составляющая интернета и компьютерной индустрии в целом. Без него всё было бы сложно и хаотично.

Unicode сам по себе не является кодировкой, а больше похож на базу данных почти всех возможных символов. В нём есть кодовая точка (идентификатор для каждого символа в базе данных), которая может иметь значение от 0 до 1,1 миллиона – как видите, скорее всего в ближайшее время эти уникальные кодовые точки не закончатся. Каждая кодовая точка в Unicode обозначается U+n, где U+ — кодовая точка Unicode, а n — это набор для символа из четырех-шести шестнадцатеричных цифр. Unicode намного надежнее ASCII, в котором только 128 символов. Обмен цифровым текстом с помощью ASCII сложнее, так как он основан на американском английском и не поддерживает символы с диакритическими знаками. А в Unicode почти 150 000 символов и он охватывает символы всех языков мира.

Поэтому от языков программирования и требуется правильно обрабатывать текст и обеспечивать интернационализацию программного обеспечения. Python используют для разных целей — от электронной почты до серверов и интернета —  у него отличный способ обработки Unicode, он принимает стандарт Unicode для своих строк.

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

Требования

Нам понадобится:

1: Конвертирование кодовых точек Unicode в Python

Кодирование — это процесс представления данных в читаемой компьютером форме. Существуют разные способы кодирования данных — ASCII, Latin-1 и т. д. У каждой кодировки свои сильные и слабые стороны, но пожалуй, самой распространенной является UTF-8 —  тип кодирования, который отображает символы со всего мира в одном наборе. То есть, UTF-8 это незаменимый инструмент для всех, кто работает с интернационализированными данными. В целом, UTF-8 справляется с многими задачами. Он относительно эффективен и может работать в разных программах. UTF-8 конвертирует кодовую точку Unicode в понятные компьютеру шестнадцатеричные байты. Другими словами, Unicode – это маппинг, а UTF-8 позволяет компьютеру понять этот маппинг.

В Python 3 кодировка строк по умолчанию – UTF-8, значит, любая кодовая точка Unicode в строке Python автоматически конвертируется в соответствующий символ.

Сейчас мы создадим символ авторского права (©) с помощью его кодовой точки Unicode в Python. Сначала запустите интерактивную консоль Python в терминале, а затем введите:

>>> s =  '\u00A9'
>>> s

В этом коде мы создали строку s с кодовой точкой Unicode \u00A9. Как упоминалось ранее, поскольку строка Python по умолчанию использует кодировку UTF-8, вывод значения s автоматически заменяет его на соответствующий символ Unicode. Обратите внимание, что \u в начале кода обязателен. Без него Python не сможет конвертировать кодовую точку. В выводе получим соответствующий символ Unicode:

'©'

В Python есть встроенные функции для кодирования и декодирования строк. Функция encode() конвертирует строку в байтовую строку.

Для этого откройте интерактивную консоль Python и введите код:

>>> ''.encode('utf-8')

В результате получим байтовую строку символа:

b'\xf0\x9f\x85\xa5'

Обратите внимание, что перед каждым байтом стоит \x, значит, это шестнадцатеричное число.

Примечание: Ввод спецсимволов Unicode в Windows и Mac отличается. В Windows предыдущий и все последующие примеры этого мануала, где используются символы, вы можете вставить с помощью утилиты Character Map. В Mac нет этой функции, поэтому лучше скопировать символ из примера кода.

Далее с помощью функции decode() конвертируем байтовую строку в обычную. Функция decode() принимает в качестве аргумента тип кодировки. Отметим, что функция decode() может декодировать только байтовую строку, которая задается с помощью буквы b в начале строки. Удаление b приведет к ошибке AttributeError.

В консоли введите:

>>> b'\xf0\x9f\x85\xa5'.decode('utf-8')

Получим следующий вывод:

''

Теперь у вас есть базовое понимание интерпретации Unicode в Python. Далее мы разберем встроенный в Python модуль unicodedata, чтобы применить расширенные методы Unicode для строк.

2: Нормализация Unicode в Python

С помощью нормализации можно определить, одинаковы ли два символа, написанные разными шрифтами. Это удобно, когда два символа с разными кодовыми точками дают одинаковый результат. Например, мы воспринимаем символы Unicode R и ℜ как одинаковые, поскольку они оба представляют собой букву R, но для компьютера они разные.

Следующий пример кода это демонстрирует. Откройте консоль Python и введите следующее:

>>> styled_R = 'ℜ'
>>> normal_R = 'R'
>>> styled_R == normal_R

В результате получим:

False

Вывод будет False, потому что Python не считает эти два символа одинаковыми. Именно поэтому нормализация важна при работе с Unicode.

В Unicode некоторые символы создаются путем объединения нескольких символов в один. Нормализация также важна в этом случае, потому что она обеспечивает согласованность строк. Рассмотрим это на примере. Откройте консоль Python и введите код:

>>> s1 =  'hôtel'
>>> s2 = 'ho\u0302tel'
>>> len(s1), len(s2)

В этом коде мы создали строку s1, содержащую символ ô, а строка s2 содержит кодовую точку символа циркумфлекса ( ̂ ). Получим такой вывод:

(5, 6)

Это значит, что две строки состоят из одинаковых символов, но имеют разную длину. Значит, они не подходят под условие равенства. Чтобы это проверить, введите в той же консоли:

>>> s1 == s2

Получим вывод:

False

Хотя строковые переменные s1 и s2 производят один и тот же символ Unicode, они различаются по длине и, следовательно, не равны.

Решить эту проблему можно с помощью функции normalize().

3: Нормализация Unicode с помощью NFD, NFC, NFKD и NFKC

Сейчас мы нормализуем строки Unicode с помощью функции normalize() из библиотеки unicodedata Python в модуле unicodedata (он обеспечивает поиск и нормализацию символов). Функция normalize() может принимать форму нормализации в качестве первого аргумента и нормализуемую строку в качестве второго аргумента. В Unicode существует четыре типа форм нормализации: NFD, NFC, NFKD и NFKC.

NFD разбивает символ на несколько комбинируемых символов. Это делает текст нечувствительным к диакритическим символам, что удобно при поиске и сортировке. Для этого нужно закодировать строку в байты.

Откройте консоль и введите следующее:

>>> from unicodedata import normalize
>>> s1 =  'hôtel'
>>> s2 = 'ho\u0302tel'
>>> s1_nfd = normalize('NFD', s1)
>>> len(s1), len(s1_nfd)

Получим следующий вывод:

(5, 6)

При нормализации строки s1 ее длина увеличилась на один символ. Это происходит по причине того, что символ ô разбивается на два символа — o и ˆ. Следующий код это подтвердит:

>>> s1.encode(), s1_nfd.encode()

В результате, после кодирования нормализованной строки символ o отделился от символа ˆ в строке s1_nfd:

(b'h\xc3\xb4tel', b'ho\xcc\x82tel')

Форма нормализации NFC раскладывает символ, а затем перекомпонует его с любым доступным объединяющим символом. W3C рекомендует использовать NFC в интернете, поскольку NFC компонует строку для получения максимально короткого результата. Ввод с клавиатуры по умолчанию возвращает составленные строки, поэтому в этом случае рекомендуется применять NFC.

Для примера введите в интерактивную консоль:

>>> from unicodedata import normalize
>>> s2_nfc = normalize('NFC', s2)
>>> len(s2), len(s2_nfc)

Вывод будет следующим:

(6, 5)

При нормализации строки s2 её длина уменьшилась на единицу. Введите код в интерактивной консоли:

>>> s2.encode(), s2_nfc.encode()

Вывод будет таким:

(b'ho\xcc\x82tel', b'h\xc3\xb4tel')

Символы o и ˆ соединены в один символ ô.

Формы нормализации NFKD и NFKC применяют для «строгой» нормализации и поиска, сопоставления  образцов в строках Unicode. “K” в NFKD и NFKC означает совместимость.

NFD и NFC разделяют символы, но NFKD и NFKC выполняют разделение на совместимость для непохожих эквивалентых символов, при этом удаляются любые различия форматирования. Например, строка ②① не похожа на 21, но обе имеют одно значение. Формы нормализации NFKC и NFKD удаляют форматирование (в данном случае круг вокруг цифр) из символов, чтобы представить их упрощенную форму.

На примере разберем разницу между NFD и NFKD. Откройте интерактивную консоль Python и введите:

>>> s1 = '2⁵ô'
>>> from unicodedata import normalize
>>> normalize('NFD', s1), normalize('NFKD', s1)

Получаем следующий вывод:

('2⁵ô', '25ô')

Форма NFD не смогла разделить символ экспоненты в строке s1, но NFKD вырезала форматирование экспоненты и заменила символ совместимости (в данном случае экспоненту 5) на его эквивалент (5 в виде цифры). Так как NFD и NFKD разделяют символы, следовательно, ô увеличит длину на единицу. Это подтвердит следующее:

>>> len(normalize('NFD', s1)), len(normalize('NFKD', s1))

Код возвращает:

(4, 4)

Принцип работы NFKC аналогичный, но он скорее не разделяет символы, а компонует их. В консоли Python введите:

>>> normalize('NFC', s1), normalize('NFKC', s1)

Вывод будет следующим:

('2⁵ô', '25ô')

Строка для символа ô уменьшится на единицу, поскольку в NFKC композиционный подход (в случае разделения значение увеличивается на единицу). Проверим это с помощью кода:

>>> len(normalize('NFC', s1)), len(normalize('NFKC', s1))

Получаем вывод:

(3, 3)

Мы разобрали типы нормализации и различия между ними. Далее мы устраним ошибки Unicode в Python.

4: Устранение ошибок Unicode в Python

При работе с Unicode в Python могут возникать два типа ошибок: UnicodeEncodeError и UnicodeDecodeError. Теперь разберем пути их устранения.

Устранение ошибки UnicodeEncodeError

Кодирование в Unicode — это процесс конвертирования строки Unicode в байты с помощью определенной кодировки. Ошибка UnicodeEncodeError возникает при попытке закодировать строку, символы которой не могут быть представлены в заданной кодировке.

Чтобы создать эту ошибку, давайте попробуем закодировать строку с символами, которые не входят в набор ASCII.

Откройте консоль и введите:

>>> ascii_supported = '\u0041'
>>> ascii_supported.encode('ascii')

Вывод будет:

b'A'

Затем введите:

>>> ascii_unsupported = '\ufb06'
>>> ascii_unsupported.encode('utf-8')

Получим следующий результат:

b'\xef\xac\x86'

Наконец, введите следующее:

>>> ascii_unsupported.encode('ascii')

При запуске этого кода возникнет ошибка:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>

UnicodeEncodeError: ‘ascii’ codec can’t encode character ‘\ufb06’ in position 0: ordinal not in range(128)

В ASCII ограниченное количество символов, поэтому Python выдает ошибки при нахождении символа, которого нет в кодировке ASCII. Поскольку кодовая система ASCII не распознает кодовую точку \ufb06, Python выдаст сообщение об ошибке. В нем идет речь о том, что ASCII имеет диапазон только 128 символов, а соответствующий десятичный эквивалент этой кодовой точки не входит в этот диапазон.

UnicodeEncodeError можно обработать с помощью аргумента errors в функции encode(). У аргумента errors может быть одно из трех значений: ignore, replace и xmlcharrefreplace.

Откройте консоль и введите:

>>> ascii_unsupported = '\ufb06'
>>> ascii_unsupported.encode('ascii', errors='ignore')

Получим следующий вывод:

b''

Далее введите:

>>> >>> ascii_unsupported.encode('ascii', errors='replace')

Вывод будет таким:

b'?'

Наконец, введите:

>>> ascii_unsupported.encode('ascii', errors='xmlcharrefreplace')

В результате получим:

b'&#64262;'

Во всех случаях Python не выдает ошибку. Значение

ignore пропускает символ, который не может быть закодирован; replace заменяет символ знаком ?; а xmlcharrefreplace заменяет некодируемые символы сущностью XML.

Устранение ошибки UnicodeDecodeError

Ошибка UnicodeEncodeError возникает при попытке декодировать строку, символы которой не могут быть представлены в заданной кодировке.

Чтобы создать эту ошибку, мы попробуем декодировать строку байтов в кодировку, которую невозможно декодировать.

Откройте консоль и введите:

>>> iso_supported = '§'
>>> b = iso_supported.encode('iso8859_1')
>>> b.decode('utf-8')

Получим следующую ошибку:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa7 in position 0: invalid start byte

Если вы столкнулись с этой ошибкой, можете применить аргумент errors в функции decode(), c помощью которого можно декодировать строку. Аргумент errors принимает два значения: ignore и replace.

Откройте консоль Python и введите код:

>>> iso_supported = '§A'
>>> b = iso_supported.encode('iso8859_1')
>>> b.decode('utf-8', errors='replace')

Вывод будет следующим:

'�A'

Затем введите:

>>> b.decode('utf-8', errors='ignore')

Получим такой вывод:

'A'

Значение replace в функции decode() добавляет символ �, а ignore ничего не возвращает, поскольку декодер (в данном случае utf-8) не смог декодировать байты.

При декодировании строки следует помнить, что предполагать ее кодировку нельзя, нужно точно знать, как именно она была закодирована.

Подводим итоги

В этом мануале мы рассмотрели основы работы Unicode в Python. Мы кодировали и декодировали строки, нормализовали данные с помощью NFD, NFC, NFKD и NFKC, а также устранили ошибки Unicode. Также мы применили формы нормализации в сценариях сортировки и поиска. Эти методы помогут устранить ошибки Unicode с помощью Python. Рекомендуем ознакомиться с материалами модуля unicodedata.