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 функцию нормализации и обработаем ошибки.
Требования
Нам понадобится:
- Python, установленный локально или на удаленном сервере. Если у вас еще не установлен Python, ознакомьтесь с этим мануалом.
- Знание основ программирования и строковых методов Python. Читайте Основы работы со строками в Python 3.
- Знание принципов работы с интерактивной консолью Python. Читайте мануал Использование интерактивной консоли Python.
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'st'
Во всех случаях 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.