Больше не нужно искать работу мечты — присоединяйтесь к команде Клауда

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

Роман Андреев
Роман Андреев
Технический писатель
30 марта 2023 г.
4263
10 минут чтения
Средний рейтинг статьи: 5

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

Кодировки: общая информация

В общем виде кодировка предполагает перевод любого символа в понятный компьютеру вид, когда каждая буква, число или иной знак (например, !, %, ?) записывается в двоичном виде, как последовательность нулей и единиц. В распространенной кодировке ASCII символы группируются по так называемым контрольным точкам, представляющим собой диапазоны целочисленных значений. Эти группы охватывают специальные (контрольные) символы и те, которые не отображаются, а также числа, буквы в обоих регистрах и другие специальные знаки.

Однако проблема ASCII заключается в том, что с помощью этой кодировки можно представить только 128 символов, поскольку она поддерживает лишь 7-битные значения, а 27=128. Но поскольку компьютеры оперируют преимущественно 8-битными значениями (8 бит = 1 байт или 256 возможных символов), кодировка ASCII была приведена в соответствие с этой системой. Долгое время половина значений не использовалась, и последняя 128-я кодовая (контрольная) точка записывалась так: 01111111 (это число 127 в двоичном виде, а под первым номером идет 0: 00000000).

Понятно, что этого было бы явно недостаточно для русского языка, так как кириллица в этой кодировке остается «за бортом» из-за нехватки символов. Более того, даже латинские буквы с диакритическими знаками, используемые во многих европейских языках (например, â, è, é, ş, ţ и т. д.) не могут быть представлены в ASCII. Поэтому затем возникли расширенные кодировки ASCII, использующие все 256 возможных значений.

Однако проблемой этих кодировок стало то, что они начали конфликтовать между собой, ведь в каждом расширении наборы символов были иные (разработчики отображали символы разных языков). Поэтому возникла необходимость в разработке иных кодировок, которые могли бы охватить все возможные символы на всех языках. И одной из наиболее удобных стал как раз Unicode (Юникод), появившийся в 1991 году.

При помощи Юникода можно представить более 1,11 млн. значений, чего более чем достаточно для отображения символов на языках всего мира. Здесь стоит немного остановиться на системах счисления, поскольку компьютеры не используют привычную нам десятичную, а работают преимущественно с двоичными, восьмеричными и шестнадцатеричными значениями. Это важно, поскольку, в отличие от ASCII с его восьмеричной системой счисления, Юникод использует шестнадцатеричную систему счисления, которая содержит 216 возможных символов.

Системы счисления и работа с ними в Python

Если мы попробуем представить, допустим, число 12 в шестнадцатеричном виде в Python, то получим следующее (введите в интерпретаторе первую строку из примера ниже и вы увидите, что Python выдаст вам 18):

>>> int('12', base=16)nt('12', base=16)
18

Добавим, что аргумент base указывает на кодировку. Почему так получилось? Дело в том, что первые 16 символов в такой кодировке — это значения от 0 до 9 плюс латинские буквы A-F. Проверить это несложно, введите, например:

>>> int('A', base=16)
10

A занимает 10-е место после 0-9. 16-м символом, соответственно, будет F, далее идет 11, а затем 12, то есть значение 12 занимает 18-е место в шестнадцатеричной системе счисления, потому Python и выдал нам такой результат. С помощью аргумента base мы можем запросить нужное нам число и в любой другой системе счисления. Допустим, в привычной десятичной или восьмеричной, а далее укажем и шестнадцатеричную:

>>> int('25', base=10)
25
>>> int('25', base=8)
21
>>> int('25', base=16)
37

Но будьте осторожны, поскольку функция int() принимает только целочисленные значения и значения ряда букв (от A до F), которые также выступают в качестве чисел в шестнадцатеричной системе. Понять такуют концепцию не сложно, просто вспомните, что в латыни все числа тоже передавались буквами: III, V, VII, X, LIV и т. д. Поэтому следующая запись вызовет ошибку, так как символу L числовое значение не присвоено ни в одной из систем:

>>> int('L', base=16)
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    int('L', base=16)
ValueError: invalid literal for int() with base 16: 'L'

Как видим, Python нам указал и причину ошибки: недопустимый литерал для функции int() с базовой системой счисления 16. А вот с F всё будет в порядке в шестнадцатеричной системе счисления:

>>> int('F', base=16)
15

Но не во всех остальных, где эта буква не используется в качестве числового значения, например:

>>> int('F', base=8)
Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    int('F', base=8)
ValueError: invalid literal for int() with base 8: 'F'

Кроме того, в Python существует и более удобное представление числовых значений в машинных системах счисления: двоичной, восьмеричной и шестнадцатеричной. Взгляните на следующие примеры:

>>> 10
10

>>> 0b10
2

>>> 0o10
8

>>> 0x10
16

В первом случае мы не использовали никакого префикса для числа, поэтому интерпретатор нам выдал значение числа 10 в привычной для нас десятичной системе счисления. 0b — это префикс для представления числа в двоичном виде, 0o — в восьмеричном и, наконец, 0x — в шестнадцатеричном. Таким образом, если мы хотим узнать, какое место занимает числовой символ F в шестнадцатеричной системе счисления, то нам нужно ввести в интерпретаторе следующее:

>>> 0xF
15

С остальными же префиксами (и без них) мы получим предсказуемую ошибку. Теперь мы готовы изучать Юникод.

Кодировка Unicode Python

Собственно, называть Юникод кодировкой не совсем корректно, поскольку он не извлекает биты, а только использует кодовые точки. Поэтому более правильно считать Юникод базовым набором символов. Наиболее же распространенным стандартом кодировки, использующим Unicode в качестве такого набора символов, является UTF-8, с которым вы наверняка сталкивались, если занимались конверсией текстов: например, в редакторе Notepad++. UTF-8 как раз и предназначен для конверсии символов Юникода в понятный компьютеру вид.

Давайте посмотрим на конкретных примерах, как это происходит. Для кодирования в UTF-8 будем использовать инструкцию encode(), а для декодирования — decode() (не забывайте добавлять аргумент с указанием нужной кодировки далее). Как вы сейчас убедитесь, перевод в Unicode Python особых сложностей не представляет, и вас уже не удивит, что x в начале битового символа обозначает принадлежность этого символа шестнадцатеричной системе счисления:

>>> "таймвеб".encode("utf-8")
b'\xd1\x82\xd0\xb0\xd0\xb9\xd0\xbc\xd0\xb2\xd0\xb5\xd0\xb1'
>>> b'\xd1\x82\xd0\xb0\xd0\xb9\xd0\xbc\xd0\xb2\xd0\xb5\xd0\xb1'.decode("utf-8")
'таймвеб'

Мы получили набор из 14 двухбайтовых значений. Дело в том, что каждый кириллический символ кодируется в виде двух таких значений. А вот если мы попробуем записать название латиницей и закодируем его, то получим следующее:

>>> "timeweb".encode("utf-8")b'\xd1\x82\xd0\xb0\xd0\xb9\xd0\xbc\xd0\xb2\xd0\xb5\xd0\xb1'.decode("utf-8")
b'timeweb'
>>> b'timeweb'.decode("utf-8")
'timeweb'

Для латинских символов без диакритических знаков кодировка максимально простая и понятная. А теперь давайте попробуем написать что-нибудь по-французски с их «фирменными» значками:

>>> "répéter".encode("utf-8")
b'r\xc3\xa9p\xc3\xa9ter'
>>> b'r\xc3\xa9p\xc3\xa9ter'.decode("utf-8")
'répéter'

Здесь хорошо видно, что для буквы é тоже используется свой двухбайтовый набор символов, как и для кириллицы. В результате латинские буквы без диакритических знаков в кодировке UTF-8 остались как есть, а символ é был преобразован в двухбайтовое значение.

Как подключить метод Unicode в Python

Python 3 имеет полную поддержку Юникода и даже реализован с его помощью, поэтому явного указания в начале файла .py на UTF-8 не требуется. И это значит, что, например, присвоение répéter = "~/myworks/répéter.pdf" Python воспримет без каких-либо проблем (однако оно всё равно не рекомендуется, поскольку с этими символами могут возникнуть проблемы при работе в самой системе и других программах). А еще это значит, что кодировать и декодировать значения в Python 3 можно без явного указания кодировки, то есть так:

>>> "timeweb".encode()
b'timeweb'

>>> b'timeweb'.decode()
'timeweb'
>>> "répéter".encode()
b'r\xc3\xa9p\xc3\xa9ter'

>>> b'r\xc3\xa9p\xc3\xa9ter'.decode()
'répéter'

Как видим, интерпретатор обработал все значения корректно, никаких ошибок. Однако указывать кодировку всё же рекомендуется, поскольку UTF-8 хоть и наиболее распространена, но всё же не универсальна и поддерживается пока не везде.

Функция list()

Полезным может оказаться побайтовый вызов значений закодированных символов. Делается это при помощи функции list():

>>> "таймвеб".encode("utf-8")
b'\xd1\x82\xd0\xb0\xd0\xb9\xd0\xbc\xd0\xb2\xd0\xb5\xd0\xb1'
>>> list(b'\xd1\x82\xd0\xb0\xd0\xb9\xd0\xbc\xd0\xb2\xd0\xb5\xd0\xb1')
[209, 130, 208, 176, 208, 185, 208, 188, 208, 178, 208, 181, 208, 177]

Другие кодировки в Unicode

Таблица символов Python Unicode objects поистине огромна: только основная ее часть включает 65535 символов, куда входят все латинские, кириллические, греческие, арабские и некоторые другие с различными диакритическими знаками. Остальная часть символов зарезервирована для языков с иероглифической письменностью и разнообразных значков (например, эмодзи). Но UTF-8 не единственная из используемых кодировок. Также определенную популярность имеют UTF-16 и UTF-32. И отношения между ними такие же сложные, как и между расширениями ASCII. Давайте сравним:

>>> word = "τφχψ"
>>> codedata = word.encode("utf-8")
>>> codedata.decode("utf-8")
'τφχψ'
>>> codedata.decode("utf-16")
'蓏蛏蟏裏'
>>> codedata.decode("utf-32")
Traceback (most recent call last):
  File "<pyshell#55>", line 1, in <module>
    codedata.decode("utf-32")
UnicodeDecodeError: 'utf-32-le' codec can't decode bytes in position 0-3: code point not in range(0x110000)

Как видим, греческие символы в кодировке UTF-16 преобразовались в… японские, а попытка их представления в кодировке UTF-32 и вовсе привела к ошибке. И это серьезный повод стараться использовать только общепринятую, которой является UTF-8, иначе в работе ваших программ в разных окружениях могут случаться сбои.

Встроенные функции Python

Эти полезные функции расширят ваши возможности при работе с кодировками и помогут выполнять следующее:

  • ascii() — служит для перевода значения в кодировку ASCII;
  • bin() — дает двоичное значение целого числа;
  • oct() — дает восьмеричное значение целого числа;
  • hex() — дает шестнадцатеричное значение целого числа;
  • bytes() — представляет значение в побайтовом виде;
  • str() — представляет значение в строковом виде;
  • int() — представляет значение в целочисленном виде.

Теперь конкретные примеры:

>>> ascii('répéter')
"'r\\xe9p\\xe9ter'"
>>> ascii(32)
'32'
>>> bin(32)
'0b100000'
>>> oct(32)
'0o40'
>>> hex(32)
'0x20'
>>> bytes(32)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> str(32)
'32'
>>> int('32')
32

Прямой ввод строковых значений

Благодаря тому, что Python имеет полную поддержку UTF-8, мы можем ввести в качестве строкового значения практически любой набор символов, который будет корректно обработан:

>>> japanese = '蓏蛏蟏裏'
>>> print(japanese)
蓏蛏蟏裏

Как видим, интерпретатор не выдал ошибку и в переменной japanese у нас теперь хранятся иероглифы. Еще один экзотический пример:

>>> arabic = 'ثعبان كبير غير سام'
>>> print(arabic)
ثعبان كبير غير سام
>>> 

Мы скопировали Python по-арабски и сохранили в переменной arabic. И интерпретатор тоже обработал это корректно, с учетом даже направления письма, что нетрудно проверить, попросив Python вывести эти символы в кодировке UTF-8:

>>> arabic.encode("utf-8")
b'\xd8\xab\xd8\xb9\xd8\xa8\xd8\xa7\xd9\x86 \xd9\x83\xd8\xa8\xd9\x8a\xd8\xb1 \xd8\xba\xd9\x8a\xd8\xb1 \xd8\xb3\xd8\xa7\xd9\x85'

И затем декодируем первый по счету блок:

>>> b'\xd8\xab\xd8\xb9\xd8\xa8\xd8\xa7\xd9\x86'.decode("utf-8")
'ثعبان'

Мы получили крайнее правое слово.

Таким образом, благодаря полной поддержке Юникода и встроенным функциям Python предлагает самые широкие возможности для работы с кодировкой UTF-8 и любыми другими. И теперь вы знаете, как их использовать.

Хотите внести свой вклад?
Участвуйте в нашей контент-программе за
вознаграждение или запросите нужную вам инструкцию
img-server
30 марта 2023 г.
4263
10 минут чтения
Средний рейтинг статьи: 5
Пока нет комментариев