Как найти юникод символа питон

Turns out getting this right is fairly tricky: Python 2 and Python 3 have some subtle issues with extracting Unicode code points from a string.

Up until Python 3.3, it was possible to compile Python in one of two modes:

  1. sys.maxunicode == 0x10FFFF

In this mode, Python’s Unicode strings support the full range of Unicode code points from U+0000 to U+10FFFF. One code point is represented by one string element:

>>> import sys
>>> hex(sys.maxunicode)
'0x10ffff'
>>> len(u'U0001F40D')
1
>>> [c for c in u'U0001F40D']
[u'U0001f40d']

This is the default for Python 2.7 on Linux, as well as universally on Python 3.3 and later across all operating systems.

  1. sys.maxunicode == 0xFFFF

In this mode, Python’s Unicode strings only support the range of Unicode code points from U+0000 to U+FFFF. Any code points from U+10000 through U+10FFFF are represented using a pair of string elements in the UTF-16 encoding::

>>> import sys
>>> hex(sys.maxunicode)
'0xffff'
>>> len(u'U0001F40D')
2
>>> [c for c in u'U0001F40D']
[u'ud83d', u'udc0d']

This is the default for Python 2.7 on macOS and Windows.

This runtime difference makes writing Python modules to manipulate Unicode strings as series of codepoints quite inconvenient.

The codepoints module

To solve this, I contributed a new module codepoints to PyPI:

https://pypi.python.org/pypi/codepoints/1.0

This module solves the problem by exposing APIs to convert Unicode strings to and from lists of code points, regardless of the underlying setting for sys.maxunicode::

>>> hex(sys.maxunicode)
'0xffff'
>>> snake = tuple(codepoints.from_unicode(u'U0001F40D'))
>>> len(snake)
1
>>> snake[0]
128013
>> hex(snake[0])
'0x1f40d'
>>> codepoints.to_unicode(snake)
u'U0001f40d'

Работа с кодировкой символов на Python, да и на любом другом языке, временами выглядит довольно сложной. На Stack Overflow можно найти тысячи вопросов, посвящённых таким исключениям, как UnicodeDecodeError и UnicodeEncodeError. Данное руководство призвано прояснить сложные аспекты работы с этими исключениями и продемонстрировать, что работа с текстовыми и двоичными данными на Python 3 может быть приятной. В Python хорошо реализована поддержка Юникода, однако для работы с кодировкой всё же потребуется приложить усилия.

Вводная часть статьи даст общее понимание работы с Юникодом, не привязанное к какому-то определённому языку, однако практические примеры будут приведены именно на Python, а их описание будет довольно лаконичным.

Изучив эту статью, вы:

  • Освоите концепции кодировки символов и системы нумерации;
  • Поймёте, как кодировка работает с объектами str и bytes;
  • Узнаете, как в Python поддерживается система нумерации посредством различных форм литералов int;
  • Познакомитесь со встроенными функциями языка, относящимися к кодировке и системе нумерации.

Система нумерации и кодировка символов настолько тесно связаны, что их придётся раскрыть в одном руководстве, в противном случае материал будет неполным.

Прим. Статья ориентирована на Python 3, а все примеры кода созданы с помощью оболочки CPython 3.7.2. Большая часть более ранних версий Python 3 также будут корректно обрабатывать код. Если вы всё ещё используете Python 2 и различия в обработке текста и бинарных данных между 2 и 3 версиями языка вас отпугивают, это руководство может помочь вам преодолеть барьер.

Что такое кодировка символов?

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

Независимо от того, занимаетесь вы самообразованием или получили более формальное образование в сфере IT , наверняка пару раз вы уже видели таблицу ASCII. Эта таблица — хорошее начало для изучения принципов кодировки, так как она простая и маленькая (как вы увидите дальше, даже слишком маленькая).

Она охватывает следующее:

  • Символы английского алфавита в нижнем регистре: от a до z;
  • Символы английского алфавита в верхнем регистре: от A до Z;
  • Некоторые знаки препинания и символы: например «$» или «!»;
  • Символы, отображаемые как пустое место: пробел (« »), символ новой строки, возврата каретки, горизонтальной и вертикальной табуляции и несколько других;
  • Некоторые непечатаемые символы: такие как бекспейс, «b», которые просто невозможно отобразить, так, как к примеру, букву А.

Приведём формальное определение кодировки символов.

На самом высоком уровне — это способ перевода символов (таких как буквы, знаки пунктуации, служебные знаки, пробелы и контрольные символы) в целые числа и затем непосредственно в биты. Каждый символ может быть закодирован уникальным двоичным кодом. Если вы плохо знакомы с концепцией битов, не волнуйтесь, мы вскоре о ней поговорим.

Группы символов выделяют в отдельные категории. Каждому символу соответствует кодовая точка, которую можно рассматривать просто как целое число. В таблице ASCII символы сегментированы следующим образом:

Диапазон кодовых точек Класс
от 0 до 31 Контрольные и неотображаемые символы
от 32 до 64 Знаки пунктуации, символы, числа и пробел
от 65 до 90 Буквы английского алфавита в верхнем регистре
от 91 до 96 Дополнительные графемы, такие как [ и 
от 97 до 122 Буквы английского алфавита в нижнем регистре
от 123 до 126 Дополнительные графемы, такие как { и |
127 Контрольный неотображаемый символ (DEL)

Всего кодировка ASCII содержит 128 символов. В таблице ниже вы видите исчерпывающий набор знаков, которые позволяет отобразить эта кодировка. Если вы не видите какого-то символа, значит вы просто не сможете его вывести с помощью ASCII.

Кодовая точка Символ (имя) Кодовая точка Символ (имя)
0 NUL (Null) 64 @
1 SOH (Start of Heading) 65 A
2 STX (Start of Text) 66 B
3 ETX (End of Text) 67 C
4 EOT (End of Transmission) 68 D
5 ENQ (Enquiry) 69 E
6 ACK (Acknowledgment) 70 F
7 BEL (Bell) 71 G
8 BS (Backspace) 72 H
9 HT (Horizontal Tab) 73 I
10 LF (Line Feed) 74 J
11 VT (Vertical Tab) 75 K
12 FF (Form Feed) 76 L
13 CR (Carriage Return) 77 M
14 SO (Shift Out) 78 N
15 SI (Shift In) 79 O
16 DLE (Data Link Escape) 80 P
17 DC1 (Device Control 1) 81 Q
18 DC2 (Device Control 2) 82 R
19 DC3 (Device Control 3) 83 S
20 DC4 (Device Control 4) 84 T
21 NAK (Negative Acknowledgment) 85 U
22 SYN (Synchronous Idle) 86 V
23 ETB (End of Transmission Block) 87 W
24 CAN (Cancel) 88 X
25 EM (End of Medium) 89 Y
26 SUB (Substitute) 90 Z
27 ESC (Escape) 91 [
28 FS (File Separator) 92
29 GS (Group Separator) 93 ]
30 RS (Record Separator) 94 ^
31 US (Unit Separator) 95 _
32 SP (Space) 96 `
33 ! 97 a
34 " 98 b
35 # 99 c
36 $ 100 d
37 % 101 e
38 & 102 f
39 ' 103 g
40 ( 104 h
41 ) 105 i
42 * 106 j
43 + 107 k
44 , 108 l
45 - 109 m
46 . 110 n
47 / 111 o
48 0 112 p
49 1 113 q
50 2 114 r
51 3 115 s
52 4 116 t
53 5 117 u
54 6 118 v
55 7 119 w
56 8 120 x
57 9 121 y
58 : 122 z
59 ; 123 {
60 < 124 |
61 = 125 }
62 > 126 ~
63 ? 127 DEL (delete)

Модуль string

Модуль string — простой и удобный инструмент, разграничивающий содержащиеся в ASCII символы по группам, разделяя их в строки-константы. Вот как выглядит основная часть модуля:

# From lib/python3.7/string.py

whitespace = ' tnrvf'
ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ascii_letters = ascii_lowercase + ascii_uppercase
digits = '0123456789'
hexdigits = digits + 'abcdef' + 'ABCDEF'
octdigits = '01234567'
punctuation = r"""!"#$%&'()*+,-./:;<=>?@[]^_`{|}~"""
printable = digits + ascii_letters + punctuation + whitespace

Большинство этих констант исчерпывающе описаны их идентификаторами. Мы вкратце коснёмся констант hexdigits и octdigits.

Мы можем использовать определённые в модуле константы для рутинных операций:

>>> import string

>>> s = "What's wrong with ASCII?!?!?"
>>> s.rstrip(string.punctuation)
'What's wrong with ASCII'

Прим. Обратите внимание, string.printable включает string.whitespace. Это несколько не соответствует тому, как печатаемые символы определяет метод str.isprintable(), который не рассматривает ни один из символов {'v', 'n', 'r', 'f', 't'} как печатаемый.

Это различие происходит из определения метода: str.isprintable() рассматривает что-либо печатаемым, если «все символы рассматриваются как печатаемые методом repr().

Что такое биты

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

Бит — это сигнал, который имеет два возможных состояния. Есть различные способы символического отображения этих состояний:

  • 0 или 1;
  • «да» или «нет»;
  • True или False;
  • «включено» или «выключено».

Таблица ASCII из предыдущего раздела использует то, что обычно назвали бы числами (от 0 до 127), однако для наших целей важно понимать, что это десятичные числа (с основанием 10).

Каждое из этих десятичных чисел можно выразить последовательностью бит (числом с основанием 2). Вот таблица соотношения двоичных и десятичных чисел:

Десятичное Двоичное (кратко) Двоичное (в байте)
0 0 00000000
1 1 00000001
2 10 00000010
3 11 00000011
4 100 00000100
5 101 00000101
6 110 00000110
7 111 00000111
8 1000 00001000
9 1001 00001001
10 1010 00001010

Обратите внимание, что при увеличении десятичного числа n для его отображения (а следовательно и для отображения символа, относящегося к этому числу) требуется всё больше значимых бит.

Вот удобный метод представить строки ASCII как последовательность бит. Каждый символ из строки ASCII переводится в последовательность из 8 нолей и единиц с пробелами между этими последовательностями:

>>> def make_bitseq(s: str) -> str:
...     if not s.isascii():
...         raise ValueError("ASCII only allowed")
...     return " ".join(f"{ord(i):08b}" for i in s)

>>> make_bitseq("bits")
'01100010 01101001 01110100 01110011'

>>> make_bitseq("CAPS")
'01000011 01000001 01010000 01010011'

>>> make_bitseq("$25.43")
'00100100 00110010 00110101 00101110 00110100 00110011'

>>> make_bitseq("~5")
'01111110 00110101'

Прим. Обратите внимание, что метод .isascii() появился в Python 3.7.

Строковой литерал f-string f"{ord(i):08b}" использует мини-язык форматирования Format Specification Mini-Language, а именно его возможность замещения полей при форматировании строк.

  • левая часть выражения, ord(i), представляет объект, значение которого будет отформатировано и отображено при выводе. ord() возвращает кодовую точку одиночного символа str в десятичном выражении;
  • Правая сторона выражения определяет форматирование объекта. 08 означает ширина 8, заполнение нулями, а b работает как команда вывести число в двоичном (binary) эквиваленте.

На самом деле этот метод можно использовать разве что для развлечения. Он выдаст ошибку для любого символа, не представленного в ASCII-таблице. Позже мы рассмотрим, как эта проблема решается в других кодировках.

Нам нужно больше бит

Исходя из определения бита, можно вывести следующую закономерность: при определённом количестве бит n с их помощью можно выразить 2n разных значений.

def n_possible_values(nbits: int) -> int:
    return 2 ** nbits

Вот что это означает:

  • 1 бит позволяет выразить 21 == 2 возможных значения;
  • 8 бит позволяют выразить 28 == 256 возможных значений;
  • 64 бита позволяют выразить 264 == 18 446 744 073 709 551 616 возможных значений.

В качестве естественного вывода из приведённой выше формулы мы можем установить следующее: для того, чтобы вычислить количество бит, необходимых для выражения определённого числа разных значений, нам нужно найти n в уравнении 2n=x, где переменная x известна.

Вот как можно это рассчитать:

>>> from math import ceil, log

>>> def n_bits_required(nvalues: int) -> int:
...     return ceil(log(nvalues) / log(2))

>>> n_bits_required(256)
8

Округление вверх в методе n_bits_required() требуется для расчёта значений, которые не являются чистой степенью двойки. К примеру, вам нужно сохранить набор из 110 различных символов. Для этого потребуется log(110) / log(2) == 6.781 бит, но поскольку бит для вычислительной техники является мельчайшей неделимой величиной, для отображения 110 различных значений нам понадобится 7 бит, при этом несколько значений останутся невостребованными.

>>> n_bits_required(110)
7

Всё сказанное служит для обоснования одной идеи: ASCII, строго говоря, семибитная кодировка. Эта таблица содержит 128 кодовых точек, и, соответственно, символов, от 0 до 127 включительно. Это требует 7 бит:

>>> n_bits_required(128)  # от 0 до 127
7
>>> n_possible_values(7)
128

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

Прим. В этой статье под байтом подразумевается группа из 8 бит, как повелось с 60-х годов прошлого века. Если вам не по душе это новомодное название, можете называть их октетами.

То, что ASCII-таблица использует 7 бит из доступных 8, означает, что память вычислительного устройства, занятого строками символов ASCII, наполовину пуста. Для того, чтобы лучше понять, почему это происходит, вернитесь к приведённой выше таблице соответствия двоичных и десятичных чисел. Вы можете выразить числа 0 и 1 с помощью 1 бита, или вы можете использовать 8 бит, чтобы выразить их как 00000000 и 00000001 соответственно.

Прим. перев. Если быть точным, то пустой остаётся только одна восьмая часть памяти. Однако с помощью именно этого незадействованного бита можно было бы создать вдвое больше кодовых точек.

Вы можете выразить числа от 0 до 3 всего двумя битами, от 00 до 11, или использовать 8 бит, чтобы выразить их как 00000000, 00000001, 00000010 и 00000011. Самая большая кодовая точка ASCII, 127, требует только 7 значимых бит.

С учётом этого взгляните, как метод make_bitseq() преобразует строки ASCII в строки, состоящие из байт, где каждый символ требует один байт:

>>> make_bitseq("bits")
'01100010 01101001 01110100 01110011'

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

Несмотря на попытку задействовать дополнительный бит, эти конфликтующие кодировки не могли отобразить все возможные символы, используемые человечеством в письменности.

Со временем появилась одна большая схема кодировки, которая объединила их. Однако, прежде чем мы до этого доберёмся, поговорим немного о краеугольных камнях схем кодировки символов — системах счисления.

Изучаем основы: другие системы счисления

В ASCII-таблице, как мы увидели, каждый символ соответствует числу от 0 до 127.

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

Однако существуют и другие системы счисления, которые, в частности, широко используются в исходном коде CPython. Следует понимать, что действительное число не изменяется, а системы счисления просто по-разному его выражают.

Вопрос, какое число записано в строке "11" покажется странным, ведь для большинства очевидно, что это одиннадцать.

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

  • Двоичная: с основой 2;
  • Восьмеричная: с основой 8;
  • Шестнадцатеричная (hex): с основой 16.

Что же мы подразумеваем, говоря что определённая система счисления имеет основу N?

Один из способов объяснения разных систем счисления заключается в том, чтобы представить, что у вас N пальцев.

Если же вам требуется более подробное объяснение систем счисления, обратитесь к книге Чарльза Петцольда «Код». В этой книге детально объясняются основы работы вычислительной техники.

Конструктор int() — один из способов показать, как разные системы счисления преобразуют одну и ту же строку с помощью Python. Если вы передадите str в int(), Python по умолчанию будет считать, что строка содержит число в десятичной системе. Однако вы можете дать другие указания:

>>> int('11')
11
>>> int('11', base=10)  # 10 установлено по умолчанию
11
>>> int('11', base=2)  # Двоичная
3
>>> int('11', base=8)  # Восьмеричная
9
>>> int('11', base=16)  # Шестнадцатеричная
17

Чаще в Python для обозначения того, что целое число представлено в системе счисления, отличной от десятичной, используют префиксы-литералы. Для каждой из трёх альтернативных систем существует свой литерал.

Тип литерала Префикс Пример
Нет Нет 11
Binary literal 0b или 0B 0b11
Octal literal 0o или 0O 0o11
Hex literal 0x или 0X 0x11

Всё это — разновидности целочисленных литералов. Результаты применения префиксов будут такими же, как и в случае использования int() с определением параметра base. Для Python всё это просто целые числа:

>>> 11
11
>>> 0b11  # Двоичный литерал
3
>>> 0o11  # Восьмеричный литерал
9
>>> 0x11  # Шестнадцатеричный литерал
17

В таблице ниже отражено, как можно ввести десятичные числа от 0 до 20 в двоичном, восьмеричном и шестнадцатеричном эквиваленте. Любой из этих способов можно использовать как в оболочке интерпретатора Python, так и в исходном коде, и все эти числа будут рассматриваться как относящиеся к типу int.

Десятичные Двоичные Восмеричные Шестнадцатеричные
0 0b0 0o0 0x0
1 0b1 0o1 0x1
2 0b10 0o2 0x2
3 0b11 0o3 0x3
4 0b100 0o4 0x4
5 0b101 0o5 0x5
6 0b110 0o6 0x6
7 0b111 0o7 0x7
8 0b1000 0o10 0x8
9 0b1001 0o11 0x9
10 0b1010 0o12 0xa
11 0b1011 0o13 0xb
12 0b1100 0o14 0xc
13 0b1101 0o15 0xd
14 0b1110 0o16 0xe
15 0b1111 0o17 0xf
16 0b10000 0o20 0x10
17 0b10001 0o21 0x11
18 0b10010 0o22 0x12
19 0b10011 0o23 0x13
20 0b10100 0o24 0x14

Кстати, вы можете сами убедиться, что подобные способы записи чисел очень часто используется в Стандартной Библиотеке Python. Найдите папку lib/python3.7/ в своей системе, перейдите в неё и введите команду:

$ grep -nri --include "*.py" -e "b0x" lib/python3.7

Команда сработает в любой Unix-системе с утилитой grep. С её помощью вы найдёте все шестнадцатеричные литералы. Для поиска двоичных используйте b0b, а для восьмеричных — b0o.

Для чего же нужны альтернативные литералы целых чисел? Если коротко, числа 2, 8 и 16, в отличие от 10, являются степенями двойки. Основанные на них системы счисления выражают численные значения способами, более удобными для обработки бинарными вычислительными устройствами. К примеру, 65536, или 216, в шестнадцатеричной системе просто 10000 или, используя литерал, 0x10000.

Введение в Юникод

Как видите, проблема ASCII в том, что этой таблицы недостаточно для отображения знаков, символов и глифов, использующихся во всех языках и диалектах мира. Её недостаточно даже для английского языка.

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

Вы можете представить Юникод как расширенную версию ASCII-таблицы — с 1 114 112 возможными кодовыми точками, от 0 до 1 114 111. Это 17*(216) или 0x10ffff в шестнадцатеричном представлении. Фактически, ASCII является частью Юникода, так как первые 128 символов этих кодировок полностью совпадают.

Чтобы соблюсти технические детали, сам по себе Юникод не является кодировкой. Он скорее реализуется в различных кодировках символов, как вы вскоре увидите. По структуре Юникод скорее ассоциативный массив (что-то вроде dict) или база данных, состоящая из таблицы с двумя колонками. В этой таблице разные символы (такие как "a""¢", или даже "ቈ") соотносятся с различными целыми положительными числами. Кодировка же должна предоставлять несколько больше возможностей.

Юникод содержит практически любой символ, который только можно представить, включая дополнительные непечатаемые. Например, кодовая точка 8207 соответствует отметке RTL, которая используется для смены направления письма. Она полезна в текстах, где абзацы на одном из европейских языков соседствуют с абзацами на арабских языках.

Прим. Кстати, если уж мы хотим быть совсем точны в деталях, то надо отметить ещё один факт. Исторически сложилось, что в Юникоде доступны только 1 111 998 кодовых точек.

Юникод и UTF-8

Довольно скоро стало понятно, что все необходимые символы невозможно вместить в таблицу, используя только один байт. Современные, более ёмкие кодировки требовали использования больших объёмов.

Ранее мы упоминали, что Юникод сам по себе не является кодировкой. И вот почему.

Юникод не содержит указаний по извлечению из текста бит, он работает только с кодовыми точками. В нём нет стандарта конверсии текста в двоичные данные и обратно.

Юникод является абстрактным стандартом кодировки. Для практического его применения чаще всего используют схему UTF-8. Стандарт Юникод (таблица соответствий символов кодовыми точкам) определяет несколько различных кодировок на основе единого набора символов.

Как и менее распространённые UTF-16 и UTF-32, UTF-8 — формат кодировки для отображения символов Юникода в двоичном виде, используя один или несколько байт на один символ. UTF-16 и UTF-32 мы обсудим чуть позже, но пока нам интересен UTF-8 как самый популярный формат.

Сначала требуется разобрать термины «‎‎кодирование»‎ и «‎декодирование»‎.

Кодирование и декодирование в Python 3

Тип данных str в Python 3 рассчитан на представление текста в удобном для чтения формате и может содержать любые символы Юникода.

Тип bytes, напротив, представляет двоичные данные, последовательность байт, без указания на кодировку.

Кодирование и декодирование — это процесс перехода данных из одной формы в другую.

кодировка декодировка

В методах .encode() и .decode() по умолчанию используется параметр "utf-8", однако для большей уверенности этот параметр можно определить самостоятельно:

>>> "résumé".encode("utf-8")
b'rxc3xa9sumxc3xa9'
>>> "El Niño".encode("utf-8")
b'El Nixc3xb1o'

>>> b"rxc3xa9sumxc3xa9".decode("utf-8")
'résumé'
>>> b"El Nixc3xb1o".decode("utf-8")
'El Niño'

str.encode() возвращает объект типа bytes. И литералы этого типа объектов (такие как b"rxc3xa9sumxc3xa9"), и его отображение допускают только символы ASCII.

Вот почему при вызове "El Niño".encode("utf-8"), ASCII-совместимое "El" отображается как есть, а n с тильдой экранируется в "xc3xb1". Этой с виду неудобочитаемой последовательностью представлены два байта, 0xc3 и 0xb1 в шестнадцатеричной системе:

>>> " ".join(f"{i:08b}" for i in (0xc3, 0xb1))
'11000011 10110001'

Таким образом символ ñ требует два байта для бинарного представления с помощью UTF-8.

Прим. Если вы введёте help(str.encode), скорее всего, увидите параметр по умолчанию encoding='utf-8'. Однако имейте в виду, что настройки Windows для Python 3.6 могут отличаться, поэтому использовать методы кодирования и декодирования без указания необходимой кодировки (например "résumé".encode()) следует с осторожностью.

Python 3: всё на Юникоде

Python 3 полностью реализован на Юникоде, а точнее на UTF-8. Вот что это означает:

  • По умолчанию предполагается, что исходный код Python 3 написан с помощью UTF-8. Это значит, что вам не нужно использовать определение # -*- coding: UTF-8 -*- в начале файлов .py в этой версии языка.
  • Все тексты (объекты формата str) реализованы на Юникоде. Кодированный текст представлен двоичными данными (bytes). Тип strможет содержать любой символ-литерал из Юникода (например "Δv / Δt"), и все они хранятся в Юникоде.
  • Любой из символов Юникода приемлем в качестве идентификатора. Например, вы можете использовать выражение résumé = "~/Documents/resume.pdf".
  • В модуле re по умолчанию установлен флаг re.UNICODE, а не re.ASCII. Это означает, что r"w" соответствует буквам из Юникода, а не просто символам ASCII.
  • По умолчаниюencoding в str.encode() в bytes.decode() установлен в UTF-8.

Нужно отметить также нюанс, касающийся встроенного метода open(). Его параметр encoding зависит от платформы и определяется значением locale.getpreferredencoding():

>>> # Mac OS X High Sierra
>>> import locale
>>> locale.getpreferredencoding()
'UTF-8'

>>> # Windows Server 2012; другие сборки Windows могут использовать UTF-16
>>> import locale
>>> locale.getpreferredencoding()
'cp1252'

Мы делаем упор на эти моменты, чтобы вы вдруг не подумали, что кодировка UTF-8 является универсальной. Она действительно широко распространена, но вы вполне можете столкнуться и с другими вариантами. Не будет лишним предусмотреть это в коде.

Один байт, два байта, три байта, четыре…

Одна из важнейших особенностей UTF-8 состоит в том, что это кодировка с переменным размером.

Вспомните раздел, посвящённый ASCII. Любой символ в этой таблице требует максимум одного байта пространства. Это можно быстро проверить с помощью следующего генератора:

>>> all(len(chr(i).encode("ascii")) == 1 for i in range(128))
True

С UTF-8 дела обстоят по-другому. Символы Юникода могут занимать от одного до четырёх байт. Вот пример четырёхбайтного символа:

>>> ibrow = "?"
>>> len(ibrow)
1
>>> ibrow.encode("utf-8")
b'xf0x9fxa4xa8'
>>> len(ibrow.encode("utf-8"))
4

>>> # Вызов list() с объектом типа bytes возвращает
>>> # значение каждого байта
>>> list(b'xf0x9fxa4xa8')
[240, 159, 164, 168]

Это небольшая, но важная особенность метода len():

  • Размер единичного символа Юникода в объекте str языка Python всегда будет равен 1, вне зависимости от количества занимаемых байт.
  • Длина того же символа в объекте типа bytes будет варьироваться от 1 до 4.

Таблица ниже показывает, сколько байт занимают основные типы символов.

Десятичный диапазон Шестнадцатеричный
диапазон
Включённые символы Примеры
от 0 до 127 от "u0000" до "u007F" U.S. ASCII "A""n""7""&"
от 128 до 2047 от "u0080" до "u07FF" Большая часть латинских алфавитов* "ę""±""ƌ""ñ"
от 2048 до 65535 от "u0800" до "uFFFF" Дополнительные части многоязыковых символов (BMP)** "ത""ᄇ""ᮈ""‰"
от 65536 до 1114111 от "U00010000" до "U0010FFFF" Другое*** "?""?""?""?",

*Такие как английский, арабский, греческий, ирландский.
**Масса языков и символов, в основном китайский, японский и корейский с разделением по томам (а также ASCII и латиница).
***Дополнительные символы китайского, японского, корейского и вьетнамского, а также другие символы и эмоджи.

Прим. У UTF-8 есть и другие технические особенности. Те, кто работает на Python, редко с ними сталкиваются, поэтому мы не будем раскрывать их в этой статье, но упомянем вкратце, чтобы сохранить полноту картины. Так, UTF-8 использует коды-префиксы, указывающие на количество байт в последовательности. Такой приём позволяет декодеру группировать байты в условиях кодировки с переменным размером. Количество байт в последовательности определяется первым её байтом. Другие технические подробности можно найти на странице Википедии, посвящённой UTF-8 или на официальном сайте.

Особенности UTF-16 и UTF-32

Рассмотрим альтернативные кодировки, UTF-16 и UTF-32. Различие между ними и UTF-8 в основном практическое. Продемонстрируем величину расхождения с помощью перевода туда и обратно:

>>> letters = "αβγδ"
>>> rawdata = letters.encode("utf-8")
>>> rawdata.decode("utf-8")
'αβγδ'
>>> rawdata.decode("utf-16")  # ?
'뇎닎돎듎'

В данном случае, когда мы кодируем четыре буквы греческого алфавита в двоичные данные с помощью UTF-8, а декодируем обратно в текст с использованием UTF-16, на выходе получается строка с совершенно другими символами (из корейского алфавита).

Так происходит, если для кодирования и декодирования применяют разные кодировки. Два варианта декодирования одного бинарного объекта могут вернуть текст даже на другом языке.

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

Кодировка Байт на символ (включительно) Варьируемая длина
UTF-8 От 1 до 4 Да
UTF-16 От 2 до 4 Да
UTF-32 4 Нет

Любопытный аспект семейства UTF: UTF-8 не всегда занимает меньше памяти, чем UTF-16. Хотя с точки зрения математики это выглядит маловероятным, однако это возможно:

>>> text = "記者 鄭啟源 羅智堅"
>>> len(text.encode("utf-8"))
26
>>> len(text.encode("utf-16"))
22

Так получается из-за того, что кодовые точки в диапазоне от U+0800 до U+FFFF (от 2048 до 65535 в десятичной системе) в кодировке UTF-8 занимают три байта, а в UTF-16 только два.

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

Кроме того, в 2019 году компьютерная память стоит дёшево, и экономия четырёх байт за счёт использования нестандартной кодировки вряд ли стоит усилий.

Прим. перев. Есть и более весомые причины использовать UTF-8. Среди них её обратная совместимость с ASCII, а также то, что это самосинхронизирующаяся кодировка.

Вы освоили самую сложную часть статьи. Теперь посмотрим, как всё изученное реализуется на Python.

В Python есть несколько встроенных функций, каким-либо образом относящихся к системам счисления и кодировке:

  • ascii()
  • bin()
  • bytes()
  • chr()
  • hex()
  • int()
  • oct()
  • ord()
  • str()

Логически их можно сгруппировать по назначению.

  • ascii()bin()hex() и oct() предназначены для различного представления вводных данных. Все они возвращают str. Первая, ascii(), производит представление объекта в ASCII, экранируя не входящие в эту таблицу символы. Оставшиеся три дают соответственно двоичное, шестнадцатеричное и восьмеричное представление целого числа. Все эти функции меняют только представление объекта, не изменяя непосредственно вводные данные.
  • bytes()str() и int() — конструкторы классов соответствующих типов: bytesstr, и int. Все они предлагают способы подогнать данные под желаемый тип.
  • ord() и chr() выполняют противоположные действия. ord() конвертирует символ в десятичную кодовую точку, а chr() принимает в качестве аргумента целое число, и возвращает символ, кодовой точкой которого это число является.

В таблице ниже эти функции разобраны более подробно:

Функция Форма Тип аргументов Тип возвращаемых данных Назначение
ascii() ascii(obj) Различный str Представление объекта символами ASCII. Не входящие в таблицу символы экранируются
bin() bin(number) number: int str Бинарное представление целого чиста с префиксом "0b"
bytes() bytes(последовательность_целых_чисел)

bytes(s, enc[, errors])

bytes(байты_или_буфер)

bytes([i])

Различный bytes Приводит аргумент к двоичным данным, типу bytes
chr() chr(i) i: int

i>=0

i<=1114111

str Преобразует кодовую точку (целочисленное значение) в символ Юникода
hex() hex(number) number: int str Шестнадцатеричное представление целого числа с префиксом "0x"
int() int([x])

int(x, base=10)

Различный int Приводит аргумент к типу int
oct() oct(number) number: int str Восьмеричное представление целого числа с префиксом "0o"
ord() ord(c) c: str

len(c) == 1

int Возвращает значение кодовой точки символа Юникода
str() str(object=’‘)

str(b[, enc[, errors]])

Различный str Приводит аргумент к текстовому представлению, типу str

Дальше можно посмотреть полезные примеры использования этих функций.

ascii():

>>> ascii("abcdefg")
"'abcdefg'"

>>> ascii("jalepeño")
"'jalepe\xf1o'"

>>> ascii((1, 2, 3))
'(1, 2, 3)'

>>> ascii(0xc0ffee)  # Шестнадцатеричный литерал (int)
'12648430'

bin():

>>> bin(0)
'0b0'

>>> bin(400)
'0b110010000'

>>> bin(0xc0ffee)  # Шестнадцатеричный литерал (int)
'0b110000001111111111101110'

>>> [bin(i) for i in [1, 2, 4, 8, 16]]  # `int` + обработка списка
['0b1', '0b10', '0b100', '0b1000', '0b10000']

bytes():

>>> # Последовательность целых чисел
>>> bytes((104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100))
b'hello world'

>>> bytes(range(97, 123))  # Последовательность целых чисел
b'abcdefghijklmnopqrstuvwxyz'

>>> bytes("real ?", "utf-8")  # Строка + кодировка
b'real xf0x9fx90x8d'

>>> bytes(10)
b'x00x00x00x00x00x00x00x00x00x00'

>>> bytes.fromhex('c0 ff ee')
b'xc0xffxee'

>>> bytes.fromhex("72 65 61 6c 70 79 74 68 6f 6e")
b'realpython'

chr():

>>> chr(97)
'a'

>>> chr(7048)
'ᮈ'

>>> chr(1114111)
'U0010ffff'

>>> chr(0x10FFFF)  # Шестнадцатеричный литерал (int)
'U0010ffff'

>>> chr(0b01100100)  # Двоичный литерал (int)
'd'

hex():

>>> hex(100)
'0x64'

>>> [hex(i) for i in [1, 2, 4, 8, 16]]
['0x1', '0x2', '0x4', '0x8', '0x10']

>>> [hex(i) for i in range(16)]
['0x0', '0x1', '0x2', '0x3', '0x4', '0x5', '0x6', '0x7',
 '0x8', '0x9', '0xa', '0xb', '0xc', '0xd', '0xe', '0xf']

int():

>>> int(11.0)
11

>>> int('11')
11

>>> int('11', base=2)
3

>>> int('11', base=8)
9

>>> int('11', base=16)
17

>>> int(0xc0ffee - 1.0)
12648429

>>> int.from_bytes(b"x0f", "little")
15

>>> int.from_bytes(b'xc0xffxee', "big")
12648430

oct():

>>> ord("a")
97

>>> ord("ę")
281

>>> ord("ᮈ")
7048

>>> [ord(i) for i in "hello world"]
[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]

str():

>>> str("str of string")
'str of string'

>>> str(5)
'5'

>>> str([1, 2, 3, 4])  # Like [1, 2, 3, 4].__str__(), but use str()
'[1, 2, 3, 4]'

>>> str(b"xc2xbc cup of flour", "utf-8")
'¼ cup of flour'

>>> str(0xc0ffee)
'12648430'

Литералы для строк на Python

Вместо использования конструктора str(), объект этого типа чаще вводят напрямую:

>>> meal = "shrimp and grits"

Выглядит достаточно просто. Но есть один аспект, о котором нужно помнить. Поскольку Python позволяет использовать все возможности Юникода, можно «напечатать» символы, которых вы никогда не найдёте на клавиатуре. Можно скопировать и вставить их прямо в оболочку интерпретатора:

>>> alphabet = 'αβγδεζηθικλμνξοπρςστυφχψ'
>>> print(alphabet)
αβγδεζηθικλμνξοπρςστυφχψ

Кроме ввода через консоль реальных, неэкранированых символов Юникода, существуют и другие способы ввода текстовых строк.

Самые насыщенные разделы документации Python посвящены лексическому анализу. В частности, раздел о строках и литералах. Возможно, для понимания данного аспекта языка этот раздел придётся неоднократно перечитать.

Кроме прочего, там говорится о шести возможных способах ввода одного символа Юникода.

Первый, и самый распространённый метод, как вы уже видели — прямой ввод. Проблема состоит в поиске необходимых сочетаний клавиш. Здесь и могут пригодиться другие способы получения и представления символов. Вот полный список:

Экранирующая последовательность Значение Как отобразить "a"
"ooo" Символ с восьмеричным значением ooo "141"
"xhh" Символ с шестнадцатеричным значением hh "x61"
"N{name}" Символ с именем name в базе данных Юникода "N{LATIN SMALL LETTER A}"
"uxxxx" Символ с шестнадцатибитным (двухбайтным) шестнадцатеричным значением xxxx "u0061"
"Uxxxxxxxx" Символ с тридцатидвухбитным (четырёхбайтным) шестнадцатеричным значением xxxxxxxx "U00000061"

Это соответствие можно проверить на практике:

>>> (
...     "a" ==
...     "x61" == 
...     "N{LATIN SMALL LETTER A}" ==
...     "u0061" ==
...     "U00000061"
... )
True

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

  1. Не каждый способ работает со всеми символами. Шестнадцатеричное представление числа 300 выглядит как 0x012c, а это значение просто не поместится в экранирующий код "xhh", так как в нём допускаются всего две цифры. Самая большая кодовая точка, которую можно втиснуть в этот формат — "xff" ("ÿ"). Аналогичо "ooo" можно использовать только до "777" ("ǿ").
  2. Для xhhuxxxx, и Uxxxxxxxx требуется вводить ровно столько цифр, сколько указано в примерах. Это может стать неприятным сюрпризом, поскольку обычно основанные на Юникоде таблицы содержат кодовые точки для символов с префиксом U+ и варьирующимся количеством шестнадцатеричных символов. В этих таблицах кодовые точки отображают только значимые цифры.

Например, если вы обратитесь к сайту unicode-table.com с целью получить данные готического символа faihu (или fehu), "?", его кодовая точка будет U+10346.

Как же можно разместить его в "uxxxx" или "Uxxxxxxxx"? В "uxxxx" эту кодовую точку вместить невозможно, поскольку она соответствует четырёхбайтному символу. А чтобы представить его в "Uxxxxxxxx", придётся выровнять последовательность с левой стороны:

>>> "U00010346"
'?'

Это также значит, что экранирующая последовательность "Uxxxxxxxx" — единственная последовательность, способная вместить любой символ Юникода.

Прим. Вот код небольшой, но удобной функции, переводящей записи типа "U+10346" в приемлемый для Python формат с помощью str.zfill():

>>> def make_uchr(code: str):
...     return chr(int(code.lstrip("U+").zfill(8), 16))
>>> make_uchr("U+10346")
'?'
>>> make_uchr("U+0026")
'&'

Другие поддерживаемые Python кодировки

Пока что мы рассказали про 4 разные кодировки символов:

  1. ASCII;
  2. UTF-8;
  3. UTF-16;
  4. UTF-32.

Однако существует большое количество и других вариантов кодировки.

Один из примеров — Latin-1 (другое название ISO-8859-1). Это базовая кодировка для Hypertext Transfer Protocol (HTTP) в спецификации RFC 2616. Для Windows существует собственный вариант Latin-1, который называется cp1252.

Прим. Кодировка ISO-8859-1 всё ещё широко используется. Библиотека requests неукоснительно придерживается спецификации RFC 2616, используя её по умолчанию для содержимого отзывов HTTP/HTTPS. Если в заголовке Content-Type находится слово «text» и не выбрана другая кодировка, requests использует ISO-8859-1.

Полный список допустимых кодировок можно найти в документации модуля codecs, входящего в набор стандартных библиотек Python.

Среди этих кодировок стоит упомянуть ещё одну, зачастую весьма полезную. Это "unicode-escape". Если вы декодировали str и хотите быстро получить представление содержащихся в ней экранированных литералов Юникода, можно определить эту кодировку в .encode:

>>> alef = chr(1575)  # Или "u0627"
>>> alef_hamza = chr(1571)  # Или "u0623"
>>> alef, alef_hamza
('ا', 'أ')
>>> alef.encode("unicode-escape")
b'\u0627'
>>> alef_hamza.encode("unicode-escape")
b'\u0623'

Вы знаете, что говорят насчёт предположений…

Хотя Python по умолчанию предполагает, что файлы и код созданы на основе кодировки UTF-8, вам, как программисту, не следует делать аналогичное предположение относительно сторонних данных.

Когда вы получаете данные в двоичном коде из внешних источников, из файла или по сетевому соединению, стоит проверить, указана ли кодировка. Если нет — вы можете уточнить.

Все операции ввода-вывода осуществляют в байтах, наборе нулей и единиц, пока вы не сообщите системе кодировку для преобразования этих данных в текст.

Приведём пример того, что может пойти не так. Допустим, вы подписаны на API, который передаёт вам рецепт блюда дня. Вы получаете его в формате bytes и раньше всегда без проблем декодировали с использованием .decode("utf-8") . Но именно в этот день часть рецепта выглядела так:

>>> data = b"xbc cup of flour"

Похоже, нам потребуется мука, но сколько?

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

А вот и та самая неприятная ошибка UnicodeDecodeError. Подобное вполне может произойти, когда вы делаете предположение об используемой кодировке. Уточняем у разработчика ресурса, предоставляющего API. Выясняется, что полученный вами файл был закодирован с помощью  Latin-1:

>>> data.decode("latin-1")
'¼ cup of flour'

Именно в этом и крылась проблема. В Latin-1 каждый символ кодируется одним байтом, в вот в UTF-8 символ «¼» требует два байта ("xc2xbc").

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

Если уж у вас нет другого выхода и кодировку приходится угадывать, обратите внимание на библиотеку chardet. В ней используются разработанные в Mozilla методы, позволяющие сделать обоснованное предположение насчёт кодировки данных. Однако учтите, что такие инструменты должны быть вашим последним средством, не стоит прибегать к ним, если есть возможность решить вопрос другим способом.

Всякая всячина: unicodedata

Нельзя не упомянуть также модуль unicodedata. Он позволяет взаимодействовать с базой данных символов Юникода (Unicode Character Database, UCD).

>>> import unicodedata

>>> unicodedata.name("€")
'EURO SIGN'
>>> unicodedata.lookup("EURO SIGN")
'€'

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

Итак, в этой статье вы познакомились со следующими концепциями кодировки символов в Python:

  • Фундаментальные принципы кодировки символов и систем счисления;
  • Целочисленные, двоичные, восьмеричные, шестнадцатеричные, строковые и байтовые литералы в Python;
  • Встроенные функции языка, работающие с кодировкой и системами счисления;
  • Особенности обработки текстовых и двоичных данных.

Дополнительные источники

Ещё больше информации можно получить из следующих материалов (на английском языке):

  • UTF-8 Everywhere Manifesto.
  • Joel Spolsky: Минимальный уровень знаний о Юникоде и наборах символов, требующийся каждому разработчику ПО (Без отговорок!). 
  • David Zentgraf: Что обязательно должен знать о кодировках и наборах символов каждый программист для работы с текстом. 
  • Mozilla: Комплексный подход к определению языков и кодировок.
  • Wikipedia.
  • John Skeet: Юникод и .NET.
  • Network Working Group, RFC 3629: UTF-8, формат преобразования ISO 10646.
  • Unicode Technical Standard #18: Регулярные выражения Юникода.

В документации языка нашему вопросу посвящены два раздела:

  • What’s New in Python 3.0;
  • Unicode HOWTO.

Перевод статьи Unicode & Character Encodings in Python: A Painless Guide

I’m new to python and maybe this question is not so smart, but anyway I cannot solve this small issue.
As usual, for instance in a conditional statement, to find some a character or a substring in a string I’m used to write the following code:

if 'a' in myvariable:
    <do something>

However, if the character or substring are unicode characters with an high code-point, for instance a ⸣ (half square-bracket), I get the following error:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128).

I understand the problem, but I cannot figure it out how to solve it.

Of course I’m working with python 2.7

EDIT

This is my true iteration and some clarifications follow:

if '⸣' not in myvariable:
    newvariable = 100.0

I have to test if ‘⸣’ is not in myvariable: myvariable type is already <type 'unicode'>, whereas the unicode character ‘⸣’ (Unicode Code Point U+2E23) is out of the range of ASCII characters.
Moreover the scripts already make use of the pragma # -*- coding: utf-8 -*-.

Many thanks to all

Release: 1.12

This HOWTO discusses Python support for Unicode, and explains
various problems that people commonly encounter when trying to work
with Unicode.

Introduction to Unicode¶

History of Character Codes¶

In 1968, the American Standard Code for Information Interchange, better known by
its acronym ASCII, was standardized. ASCII defined numeric codes for various
characters, with the numeric values running from 0 to 127. For example, the
lowercase letter ‘a’ is assigned 97 as its code value.

ASCII was an American-developed standard, so it only defined unaccented
characters. There was an ‘e’, but no ‘é’ or ‘Í’. This meant that languages
which required accented characters couldn’t be faithfully represented in ASCII.
(Actually the missing accents matter for English, too, which contains words such
as ‘naïve’ and ‘café’, and some publications have house styles which require
spellings such as ‘coöperate’.)

For a while people just wrote programs that didn’t display accents.
In the mid-1980s an Apple II BASIC program written by a French speaker
might have lines like these:

PRINT "MISE A JOUR TERMINEE"
PRINT "PARAMETRES ENREGISTRES"

Those messages should contain accents (terminée, paramètre, enregistrés) and
they just look wrong to someone who can read French.

In the 1980s, almost all personal computers were 8-bit, meaning that bytes could
hold values ranging from 0 to 255. ASCII codes only went up to 127, so some
machines assigned values between 128 and 255 to accented characters. Different
machines had different codes, however, which led to problems exchanging files.
Eventually various commonly used sets of values for the 128–255 range emerged.
Some were true standards, defined by the International Organization for
Standardization, and some were de facto conventions that were invented by one
company or another and managed to catch on.

255 characters aren’t very many. For example, you can’t fit both the accented
characters used in Western Europe and the Cyrillic alphabet used for Russian
into the 128–255 range because there are more than 128 such characters.

You could write files using different codes (all your Russian files in a coding
system called KOI8, all your French files in a different coding system called
Latin1), but what if you wanted to write a French document that quotes some
Russian text? In the 1980s people began to want to solve this problem, and the
Unicode standardization effort began.

Unicode started out using 16-bit characters instead of 8-bit characters. 16
bits means you have 2^16 = 65,536 distinct values available, making it possible
to represent many different characters from many different alphabets; an initial
goal was to have Unicode contain the alphabets for every single human language.
It turns out that even 16 bits isn’t enough to meet that goal, and the modern
Unicode specification uses a wider range of codes, 0 through 1,114,111 (
0x10FFFF in base 16).

There’s a related ISO standard, ISO 10646. Unicode and ISO 10646 were
originally separate efforts, but the specifications were merged with the 1.1
revision of Unicode.

(This discussion of Unicode’s history is highly simplified. The
precise historical details aren’t necessary for understanding how to
use Unicode effectively, but if you’re curious, consult the Unicode
consortium site listed in the References or
the Wikipedia entry for Unicode
for more information.)

Definitions¶

A character is the smallest possible component of a text. ‘A’, ‘B’, ‘C’,
etc., are all different characters. So are ‘È’ and ‘Í’. Characters are
abstractions, and vary depending on the language or context you’re talking
about. For example, the symbol for ohms (Ω) is usually drawn much like the
capital letter omega (Ω) in the Greek alphabet (they may even be the same in
some fonts), but these are two different characters that have different
meanings.

The Unicode standard describes how characters are represented by code
points
. A code point is an integer value, usually denoted in base 16. In the
standard, a code point is written using the notation U+12CA to mean the
character with value 0x12ca (4,810 decimal). The Unicode standard contains
a lot of tables listing characters and their corresponding code points:

0061    'a'; LATIN SMALL LETTER A
0062    'b'; LATIN SMALL LETTER B
0063    'c'; LATIN SMALL LETTER C
...
007B    '{'; LEFT CURLY BRACKET

Strictly, these definitions imply that it’s meaningless to say ‘this is
character U+12CA‘. U+12CA is a code point, which represents some particular
character; in this case, it represents the character ‘ETHIOPIC SYLLABLE WI’. In
informal contexts, this distinction between code points and characters will
sometimes be forgotten.

A character is represented on a screen or on paper by a set of graphical
elements that’s called a glyph. The glyph for an uppercase A, for example,
is two diagonal strokes and a horizontal stroke, though the exact details will
depend on the font being used. Most Python code doesn’t need to worry about
glyphs; figuring out the correct glyph to display is generally the job of a GUI
toolkit or a terminal’s font renderer.

Encodings¶

To summarize the previous section: a Unicode string is a sequence of code
points, which are numbers from 0 through 0x10FFFF (1,114,111 decimal). This
sequence needs to be represented as a set of bytes (meaning, values
from 0 through 255) in memory. The rules for translating a Unicode string
into a sequence of bytes are called an encoding.

The first encoding you might think of is an array of 32-bit integers. In this
representation, the string “Python” would look like this:

   P           y           t           h           o           n
0x50 00 00 00 79 00 00 00 74 00 00 00 68 00 00 00 6f 00 00 00 6e 00 00 00
   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

This representation is straightforward but using it presents a number of
problems.

  1. It’s not portable; different processors order the bytes differently.
  2. It’s very wasteful of space. In most texts, the majority of the code points
    are less than 127, or less than 255, so a lot of space is occupied by 0x00
    bytes. The above string takes 24 bytes compared to the 6 bytes needed for an
    ASCII representation. Increased RAM usage doesn’t matter too much (desktop
    computers have gigabytes of RAM, and strings aren’t usually that large), but
    expanding our usage of disk and network bandwidth by a factor of 4 is
    intolerable.
  3. It’s not compatible with existing C functions such as strlen(), so a new
    family of wide string functions would need to be used.
  4. Many Internet standards are defined in terms of textual data, and can’t
    handle content with embedded zero bytes.

Generally people don’t use this encoding, instead choosing other
encodings that are more efficient and convenient. UTF-8 is probably
the most commonly supported encoding; it will be discussed below.

Encodings don’t have to handle every possible Unicode character, and most
encodings don’t. The rules for converting a Unicode string into the ASCII
encoding, for example, are simple; for each code point:

  1. If the code point is < 128, each byte is the same as the value of the code
    point.
  2. If the code point is 128 or greater, the Unicode string can’t be represented
    in this encoding. (Python raises a UnicodeEncodeError exception in this
    case.)

Latin-1, also known as ISO-8859-1, is a similar encoding. Unicode code points
0–255 are identical to the Latin-1 values, so converting to this encoding simply
requires converting code points to byte values; if a code point larger than 255
is encountered, the string can’t be encoded into Latin-1.

Encodings don’t have to be simple one-to-one mappings like Latin-1. Consider
IBM’s EBCDIC, which was used on IBM mainframes. Letter values weren’t in one
block: ‘a’ through ‘i’ had values from 129 to 137, but ‘j’ through ‘r’ were 145
through 153. If you wanted to use EBCDIC as an encoding, you’d probably use
some sort of lookup table to perform the conversion, but this is largely an
internal detail.

UTF-8 is one of the most commonly used encodings. UTF stands for “Unicode
Transformation Format”, and the ‘8’ means that 8-bit numbers are used in the
encoding. (There are also a UTF-16 and UTF-32 encodings, but they are less
frequently used than UTF-8.) UTF-8 uses the following rules:

  1. If the code point is < 128, it’s represented by the corresponding byte value.
  2. If the code point is >= 128, it’s turned into a sequence of two, three, or
    four bytes, where each byte of the sequence is between 128 and 255.

UTF-8 has several convenient properties:

  1. It can handle any Unicode code point.
  2. A Unicode string is turned into a sequence of bytes containing no embedded zero
    bytes. This avoids byte-ordering issues, and means UTF-8 strings can be
    processed by C functions such as strcpy() and sent through protocols that
    can’t handle zero bytes.
  3. A string of ASCII text is also valid UTF-8 text.
  4. UTF-8 is fairly compact; the majority of commonly used characters can be
    represented with one or two bytes.
  5. If bytes are corrupted or lost, it’s possible to determine the start of the
    next UTF-8-encoded code point and resynchronize. It’s also unlikely that
    random 8-bit data will look like valid UTF-8.

References¶

The Unicode Consortium site has character charts, a
glossary, and PDF versions of the Unicode specification. Be prepared for some
difficult reading. A chronology of the
origin and development of Unicode is also available on the site.

To help understand the standard, Jukka Korpela has written an introductory
guide to reading the
Unicode character tables.

Another good introductory article
was written by Joel Spolsky.
If this introduction didn’t make things clear to you, you should try
reading this alternate article before continuing.

Wikipedia entries are often helpful; see the entries for “character encoding” and UTF-8, for example.

Python’s Unicode Support¶

Now that you’ve learned the rudiments of Unicode, we can look at Python’s
Unicode features.

The String Type¶

Since Python 3.0, the language features a str type that contain Unicode
characters, meaning any string created using "unicode rocks!", 'unicode
rocks!'
, or the triple-quoted string syntax is stored as Unicode.

The default encoding for Python source code is UTF-8, so you can simply
include a Unicode character in a string literal:

try:
    with open('/tmp/input.txt', 'r') as f:
        ...
except OSError:
    # 'File not found' error message.
    print("Fichier non trouvé")

You can use a different encoding from UTF-8 by putting a specially-formatted
comment as the first or second line of the source code:

# -*- coding: <encoding name> -*-

Side note: Python 3 also supports using Unicode characters in identifiers:

répertoire = "/tmp/records.log"
with open(répertoire, "w") as f:
    f.write("testn")

If you can’t enter a particular character in your editor or want to
keep the source code ASCII-only for some reason, you can also use
escape sequences in string literals. (Depending on your system,
you may see the actual capital-delta glyph instead of a u escape.)

>>> "N{GREEK CAPITAL LETTER DELTA}"  # Using the character name
'u0394'
>>> "u0394"                          # Using a 16-bit hex value
'u0394'
>>> "U00000394"                      # Using a 32-bit hex value
'u0394'

In addition, one can create a string using the decode() method of
bytes. This method takes an encoding argument, such as UTF-8,
and optionally an errors argument.

The errors argument specifies the response when the input string can’t be
converted according to the encoding’s rules. Legal values for this argument are
'strict' (raise a UnicodeDecodeError exception), 'replace' (use
U+FFFD, REPLACEMENT CHARACTER), 'ignore' (just leave the
character out of the Unicode result), or 'backslashreplace' (inserts a
xNN escape sequence).
The following examples show the differences:

>>> b'x80abc'.decode("utf-8", "strict")  
Traceback (most recent call last):
    ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0:
  invalid start byte
>>> b'x80abc'.decode("utf-8", "replace")
'ufffdabc'
>>> b'x80abc'.decode("utf-8", "backslashreplace")
'\x80abc'
>>> b'x80abc'.decode("utf-8", "ignore")
'abc'

Encodings are specified as strings containing the encoding’s name. Python 3.2
comes with roughly 100 different encodings; see the Python Library Reference at
Standard Encodings for a list. Some encodings have multiple names; for
example, 'latin-1', 'iso_8859_1' and '8859‘ are all synonyms for
the same encoding.

One-character Unicode strings can also be created with the chr()
built-in function, which takes integers and returns a Unicode string of length 1
that contains the corresponding code point. The reverse operation is the
built-in ord() function that takes a one-character Unicode string and
returns the code point value:

>>> chr(57344)
'ue000'
>>> ord('ue000')
57344

Converting to Bytes¶

The opposite method of bytes.decode() is str.encode(),
which returns a bytes representation of the Unicode string, encoded in the
requested encoding.

The errors parameter is the same as the parameter of the
decode() method but supports a few more possible handlers. As well as
'strict', 'ignore', and 'replace' (which in this case
inserts a question mark instead of the unencodable character), there is
also 'xmlcharrefreplace' (inserts an XML character reference),
backslashreplace (inserts a uNNNN escape sequence) and
namereplace (inserts a N{...} escape sequence).

The following example shows the different results:

>>> u = chr(40960) + 'abcd' + chr(1972)
>>> u.encode('utf-8')
b'xeax80x80abcdxdexb4'
>>> u.encode('ascii')  
Traceback (most recent call last):
    ...
UnicodeEncodeError: 'ascii' codec can't encode character 'ua000' in
  position 0: ordinal not in range(128)
>>> u.encode('ascii', 'ignore')
b'abcd'
>>> u.encode('ascii', 'replace')
b'?abcd?'
>>> u.encode('ascii', 'xmlcharrefreplace')
b'ꀀabcd޴'
>>> u.encode('ascii', 'backslashreplace')
b'\ua000abcd\u07b4'
>>> u.encode('ascii', 'namereplace')
b'\N{YI SYLLABLE IT}abcd\u07b4'

The low-level routines for registering and accessing the available
encodings are found in the codecs module. Implementing new
encodings also requires understanding the codecs module.
However, the encoding and decoding functions returned by this module
are usually more low-level than is comfortable, and writing new encodings
is a specialized task, so the module won’t be covered in this HOWTO.

Unicode Literals in Python Source Code¶

In Python source code, specific Unicode code points can be written using the
u escape sequence, which is followed by four hex digits giving the code
point. The U escape sequence is similar, but expects eight hex digits,
not four:

>>> s = "axacu1234u20acU00008000"
... #     ^^^^ two-digit hex escape
... #         ^^^^^^ four-digit Unicode escape
... #                     ^^^^^^^^^^ eight-digit Unicode escape
>>> [ord(c) for c in s]
[97, 172, 4660, 8364, 32768]

Using escape sequences for code points greater than 127 is fine in small doses,
but becomes an annoyance if you’re using many accented characters, as you would
in a program with messages in French or some other accent-using language. You
can also assemble strings using the chr() built-in function, but this is
even more tedious.

Ideally, you’d want to be able to write literals in your language’s natural
encoding. You could then edit Python source code with your favorite editor
which would display the accented characters naturally, and have the right
characters used at runtime.

Python supports writing source code in UTF-8 by default, but you can use almost
any encoding if you declare the encoding being used. This is done by including
a special comment as either the first or second line of the source file:

#!/usr/bin/env python
# -*- coding: latin-1 -*-

u = 'abcdé'
print(ord(u[-1]))

The syntax is inspired by Emacs’s notation for specifying variables local to a
file. Emacs supports many different variables, but Python only supports
‘coding’. The -*- symbols indicate to Emacs that the comment is special;
they have no significance to Python but are a convention. Python looks for
coding: name or coding=name in the comment.

If you don’t include such a comment, the default encoding used will be UTF-8 as
already mentioned. See also PEP 263 for more information.

Unicode Properties¶

The Unicode specification includes a database of information about code points.
For each defined code point, the information includes the character’s
name, its category, the numeric value if applicable (Unicode has characters
representing the Roman numerals and fractions such as one-third and
four-fifths). There are also properties related to the code point’s use in
bidirectional text and other display-related properties.

The following program displays some information about several characters, and
prints the numeric value of one particular character:

import unicodedata

u = chr(233) + chr(0x0bf2) + chr(3972) + chr(6000) + chr(13231)

for i, c in enumerate(u):
    print(i, '%04x' % ord(c), unicodedata.category(c), end=" ")
    print(unicodedata.name(c))

# Get numeric value of second character
print(unicodedata.numeric(u[1]))

When run, this prints:

0 00e9 Ll LATIN SMALL LETTER E WITH ACUTE
1 0bf2 No TAMIL NUMBER ONE THOUSAND
2 0f84 Mn TIBETAN MARK HALANTA
3 1770 Lo TAGBANWA LETTER SA
4 33af So SQUARE RAD OVER S SQUARED
1000.0

The category codes are abbreviations describing the nature of the character.
These are grouped into categories such as “Letter”, “Number”, “Punctuation”, or
“Symbol”, which in turn are broken up into subcategories. To take the codes
from the above output, 'Ll' means ‘Letter, lowercase’, 'No' means
“Number, other”, 'Mn' is “Mark, nonspacing”, and 'So' is “Symbol,
other”. See
the General Category Values section of the Unicode Character Database documentation for a
list of category codes.

Unicode Regular Expressions¶

The regular expressions supported by the re module can be provided
either as bytes or strings. Some of the special character sequences such as
d and w have different meanings depending on whether
the pattern is supplied as bytes or a string. For example,
d will match the characters [0-9] in bytes but
in strings will match any character that’s in the 'Nd' category.

The string in this example has the number 57 written in both Thai and
Arabic numerals:

import re
p = re.compile('d+')

s = "Over u0e55u0e57 57 flavours"
m = p.search(s)
print(repr(m.group()))

When executed, d+ will match the Thai numerals and print them
out. If you supply the re.ASCII flag to
compile(), d+ will match the substring “57” instead.

Similarly, w matches a wide variety of Unicode characters but
only [a-zA-Z0-9_] in bytes or if re.ASCII is supplied,
and s will match either Unicode whitespace characters or
[ tnrfv].

Reading and Writing Unicode Data¶

Once you’ve written some code that works with Unicode data, the next problem is
input/output. How do you get Unicode strings into your program, and how do you
convert Unicode into a form suitable for storage or transmission?

It’s possible that you may not need to do anything depending on your input
sources and output destinations; you should check whether the libraries used in
your application support Unicode natively. XML parsers often return Unicode
data, for example. Many relational databases also support Unicode-valued
columns and can return Unicode values from an SQL query.

Unicode data is usually converted to a particular encoding before it gets
written to disk or sent over a socket. It’s possible to do all the work
yourself: open a file, read an 8-bit bytes object from it, and convert the bytes
with bytes.decode(encoding). However, the manual approach is not recommended.

One problem is the multi-byte nature of encodings; one Unicode character can be
represented by several bytes. If you want to read the file in arbitrary-sized
chunks (say, 1024 or 4096 bytes), you need to write error-handling code to catch the case
where only part of the bytes encoding a single Unicode character are read at the
end of a chunk. One solution would be to read the entire file into memory and
then perform the decoding, but that prevents you from working with files that
are extremely large; if you need to read a 2 GiB file, you need 2 GiB of RAM.
(More, really, since for at least a moment you’d need to have both the encoded
string and its Unicode version in memory.)

The solution would be to use the low-level decoding interface to catch the case
of partial coding sequences. The work of implementing this has already been
done for you: the built-in open() function can return a file-like object
that assumes the file’s contents are in a specified encoding and accepts Unicode
parameters for methods such as read() and
write(). This works through open()‘s encoding and
errors parameters which are interpreted just like those in str.encode()
and bytes.decode().

Reading Unicode from a file is therefore simple:

with open('unicode.txt', encoding='utf-8') as f:
    for line in f:
        print(repr(line))

It’s also possible to open files in update mode, allowing both reading and
writing:

with open('test', encoding='utf-8', mode='w+') as f:
    f.write('u4500 blah blah blahn')
    f.seek(0)
    print(repr(f.readline()[:1]))

The Unicode character U+FEFF is used as a byte-order mark (BOM), and is often
written as the first character of a file in order to assist with autodetection
of the file’s byte ordering. Some encodings, such as UTF-16, expect a BOM to be
present at the start of a file; when such an encoding is used, the BOM will be
automatically written as the first character and will be silently dropped when
the file is read. There are variants of these encodings, such as ‘utf-16-le’
and ‘utf-16-be’ for little-endian and big-endian encodings, that specify one
particular byte ordering and don’t skip the BOM.

In some areas, it is also convention to use a “BOM” at the start of UTF-8
encoded files; the name is misleading since UTF-8 is not byte-order dependent.
The mark simply announces that the file is encoded in UTF-8. Use the
‘utf-8-sig’ codec to automatically skip the mark if present for reading such
files.

Unicode filenames¶

Most of the operating systems in common use today support filenames that contain
arbitrary Unicode characters. Usually this is implemented by converting the
Unicode string into some encoding that varies depending on the system. For
example, Mac OS X uses UTF-8 while Windows uses a configurable encoding; on
Windows, Python uses the name “mbcs” to refer to whatever the currently
configured encoding is. On Unix systems, there will only be a filesystem
encoding if you’ve set the LANG or LC_CTYPE environment variables; if
you haven’t, the default encoding is UTF-8.

The sys.getfilesystemencoding() function returns the encoding to use on
your current system, in case you want to do the encoding manually, but there’s
not much reason to bother. When opening a file for reading or writing, you can
usually just provide the Unicode string as the filename, and it will be
automatically converted to the right encoding for you:

filename = 'filenameu4500abc'
with open(filename, 'w') as f:
    f.write('blahn')

Functions in the os module such as os.stat() will also accept Unicode
filenames.

The os.listdir() function returns filenames and raises an issue: should it return
the Unicode version of filenames, or should it return bytes containing
the encoded versions? os.listdir() will do both, depending on whether you
provided the directory path as bytes or a Unicode string. If you pass a
Unicode string as the path, filenames will be decoded using the filesystem’s
encoding and a list of Unicode strings will be returned, while passing a byte
path will return the filenames as bytes. For example,
assuming the default filesystem encoding is UTF-8, running the following
program:

fn = 'filenameu4500abc'
f = open(fn, 'w')
f.close()

import os
print(os.listdir(b'.'))
print(os.listdir('.'))

will produce the following output:

amk:~$ python t.py
[b'filenamexe4x94x80abc', ...]
['filenameu4500abc', ...]

The first list contains UTF-8-encoded filenames, and the second list contains
the Unicode versions.

Note that on most occasions, the Unicode APIs should be used. The bytes APIs
should only be used on systems where undecodable file names can be present,
i.e. Unix systems.

Tips for Writing Unicode-aware Programs¶

This section provides some suggestions on writing software that deals with
Unicode.

The most important tip is:

Software should only work with Unicode strings internally, decoding the input
data as soon as possible and encoding the output only at the end.

If you attempt to write processing functions that accept both Unicode and byte
strings, you will find your program vulnerable to bugs wherever you combine the
two different kinds of strings. There is no automatic encoding or decoding: if
you do e.g. str + bytes, a TypeError will be raised.

When using data coming from a web browser or some other untrusted source, a
common technique is to check for illegal characters in a string before using the
string in a generated command line or storing it in a database. If you’re doing
this, be careful to check the decoded string, not the encoded bytes data;
some encodings may have interesting properties, such as not being bijective
or not being fully ASCII-compatible. This is especially true if the input
data also specifies the encoding, since the attacker can then choose a
clever way to hide malicious text in the encoded bytestream.

Converting Between File Encodings¶

The StreamRecoder class can transparently convert between
encodings, taking a stream that returns data in encoding #1
and behaving like a stream returning data in encoding #2.

For example, if you have an input file f that’s in Latin-1, you
can wrap it with a StreamRecoder to return bytes encoded in
UTF-8:

new_f = codecs.StreamRecoder(f,
    # en/decoder: used by read() to encode its results and
    # by write() to decode its input.
    codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'),

    # reader/writer: used to read and write to the stream.
    codecs.getreader('latin-1'), codecs.getwriter('latin-1') )

Files in an Unknown Encoding¶

What can you do if you need to make a change to a file, but don’t know
the file’s encoding? If you know the encoding is ASCII-compatible and
only want to examine or modify the ASCII parts, you can open the file
with the surrogateescape error handler:

with open(fname, 'r', encoding="ascii", errors="surrogateescape") as f:
    data = f.read()

# make changes to the string 'data'

with open(fname + '.new', 'w',
          encoding="ascii", errors="surrogateescape") as f:
    f.write(data)

The surrogateescape error handler will decode any non-ASCII bytes
as code points in the Unicode Private Use Area ranging from U+DC80 to
U+DCFF. These private code points will then be turned back into the
same bytes when the surrogateescape error handler is used when
encoding the data and writing it back out.

Acknowledgements¶

The initial draft of this document was written by Andrew Kuchling.
It has since been revised further by Alexander Belopolsky, Georg Brandl,
Andrew Kuchling, and Ezio Melotti.

Thanks to the following people who have noted errors or offered
suggestions on this article: Éric Araujo, Nicholas Bastin, Nick
Coghlan, Marius Gedminas, Kent Johnson, Ken Krugler, Marc-André
Lemburg, Martin von Löwis, Terry J. Reedy, Chad Whitacre.

What is a Unicode encoding?

Unicode is the encoding type or standard which contains the character set of all the languages that exist all around the globe. Each character is mapped to an integer known as a Code point. It uniquely identifies a character among the other characters. 

The Unicode encoding came into existence when languages other than English started getting used prominently. 

Advantage of using a Unicode encoding

The biggest advantage with Unicode is, it allows the use of different encoding and more diverse characters set with the same set of code points.
This makes easy for the developers from different part of the world to choose the among the characters of their choice without worrying much about the encoding.

How to get the Unicode code of a character in Python?

In Python, we have a few utility functions to work with Unicode. Let’s see how we can leverage them.

Approach 1: Using built-in ord() function

ord() function came into existence only for this purpose, it returns the Unicode code of a character passed to it.

ord(l) – Returns an integer representing the Unicode code of the character l.

How to return the Unicode code of a character using ord() ?

print(ord(u"$"))   # Unicode code of $ character

#Output
#36

print(ord(u"v"))   # Unicode code of v character

#Output 
#118

print(ord(u"⁹"))   # Unicode code of superscript 9

#Output 
#8313

print(ord(u"₅"))   # Unicode code of subscript 5

#Output 
#8325

print(ord(u"ल"))   # Unicode code of devnagri letter 'ल'

#Output 
#2354

The u prefix before the string tells us that the string is a Unicode string. Since python 3 release, it is not necessary to write the prefix u as all the string by default are Unicode string.

Bonus:

The method chr() is the inverse of the method ord().
chr() gets the character that a Unicode code point corresponds to.

Example:

print(chr(554))  # Get the character from unicode code 554
#Output 
#Ȫ

print(chr(728))  # Get the character from unicode code 728
#Output
#˘

print(chr(900))  # Get the character from unicode code 900
#Output 
#΄

print(chr(1121))  # Get the character from unicode code 1121
#Output 
#ѡ

That’s all, folks !!!

Добавить комментарий