Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Внутри CPython гид по интерпретатору Python.pdf
Скачиваний:
6
Добавлен:
07.04.2024
Размер:
8.59 Mб
Скачать

272    Объекты и типы

После повторной компиляции Python вы увидите последствия изменений:

>>>2 == 1 False

>>>2 ~= 1

True

>>>2 ~= 10 False

ТИП СТРОКИ ЮНИКОДА

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

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

В Python 2 строковый тип хранился с использованием типа char языка C. Однобайтовый тип char позволял хранить любые символы ASCII (American Standard Code for Information Interchange) и использовался в программировании с 1970-х годов.

ASCII поддерживает не все языки и алфавиты мира. Кроме того, он не поддерживает многие расширенные наборы глифов (например, эмодзи).

Для решения подобных проблем в 1991 году Консорциумом Юникода была представлена стандартная система кодирования и база данных символов, известная как «Юникод». Современный стандарт Юникода включает символы всех письменных языков, а также расширенные наборы глифов и символов.

База данных символов Юникода (UCD, Unicode Character Database) версии 13.0 содержит 143 859 именованных символов (тогда как в ASCII их всего 128). Стандарт Юникода определяет эти символы в виде таблицы, называемой универсальным набором символов, или UCS (Universal Character Set). Каждый символ имеет уникальный идентификатор, называемый

кодовой точкой.

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

Строки Юникода в Python поддерживают три варианта длины кодирования:

Книги для программистов: https://t.me/booksforits

Тип строки Юникода    273

zz 1-байтовая (8 бит) zz 2-байтовая (16 бит) zz 4-байтовая (32 бита)

В реализации кодировкам изменяемой длины соответствуют следующие обозначения:

zz 1-байтовый Py_UCS1, хранится как 8-битный тип int без знака uint8_t. zz 2-байтовый Py_UCS2, хранится как 16-битный тип int без знака uint16_t. zz 4-байтовый Py_UCS4, хранится как 32-битный тип int без знака uint32_t.

Исходные файлы

Ниже перечислены исходные файлы, относящиеся к строкам.

ФАЙЛ

НАЗНАЧЕНИЕ

Include unicodeobject.h

Определение объекта строки Юникода

Include cpython

Определение объекта строки Юникода

unicodeobject.h

 

Objects unicodeobject.c

Реализация объекта строки Юникода

Lib encodings

Пакет encodings со всеми возможными кодиров-

 

ками

Lib codecs.py

Модуль codecs

Modules _codecsmodule.c

Расширения C модуля codecs; реализация кодиро-

 

вок с привязкой к ОС

Modules _codecs

Реализация для многих альтернативных кодировок

Обработка кодовых точек Юникода

CPython не содержит копии UCD и не обновляется при добавлении новых алфавитов и символов в стандарт Юникода.

Для строк Юникода в CPython важны только кодировки. Операционная система берет на себя задачу представления кодовых точек в правильных наборах символов.

Книги для программистов: https://t.me/booksforits

274    Объекты и типы

Стандарт Юникода включает UCD и регулярно обновляется новыми наборами символов, эмодзи и отдельными символами. Операционные системы получают обновления в Юникоде и обновляют свое программное обеспечение. Обновления включают новые кодовые точки UCD и поддерживают различные кодировки Юникода. UCD разбивается на разделы, называемые

кодовыми блоками.

Таблицы символов Юникода публикуются на официальном веб-сайте1.

Другим центром поддержки Юникода является веб-браузер. Браузеры декодируют двоичные данные HTML, основываясь на кодировке, указанной в HTTP-заголовках. Если вы используете CPython как веб-сервер, то ваши кодировки Юникода должны совпадать с заголовками HTTP, отправляемыми пользователям.

UTF-8 и UTF-16

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

zz UTF-8 — 8-битная кодировка символов, поддерживающая все возможные символы UCD с кодовыми точками от 1 до 4 байтов.

zz UTF-16 — 16-битная кодировка символов, похожая на UTF-8, но несовместимая с 7- и 8-битовыми кодировками (такими, как ASCII).

Среди всех кодировок Юникода самой популярной является UTF-8.

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

zz U+00F7 для знака деления ('÷');

zz U+0107 для латинской строчной буквы c с диакритическим знаком ('ć').

В Python кодовые пункты Юникода могут кодироваться непосредственно в программном коде с префиксом \u и шестнадцатеричным значением кодовой точки:

>>> print("\u0107")

ć

1 https://unicode.org/charts/.

Книги для программистов: https://t.me/booksforits

Тип строки Юникода    275

CPython не пытается преобразовать такие данные в полную форму, так что если вы попробуете использовать запись \u107, будет выдано следующее исключение:

print("\u107")

File "<stdin>", line 1

SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 0-4: truncated \uXXXX escape

Как XML, так и HTML поддерживают кодовые точки Юникода в специальной записи val;, где val — десятичное значение кодовой точки. Если вы хотите закодировать кодовые точки Юникода в XML или HTML, используйте обработчик ошибок xmlcharrefreplace в методе .encode():

>>> "\u0107".encode('ascii', 'xmlcharrefreplace') b'ć'

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

Совместимость с ASCII

Если вы работаете с текстом, закодированным в ASCII, важно понимать, чем UTF-8 отличается от UTF-16. Главным преимуществом UTF-8 является совместимость с текстом, закодированным в 7-битной кодировке ASCII.

Первые 128 кодовых точек стандарта Юникод представляют существующие 128 символов стандарта ASCII. Например, латинская буква "a" является 97-м символом ASCII и 97-м символом Юникода. Десятичное значение 97 эквивалентно 61 в шестнадцатеричной системе, так что букве "a" будет соответствовать кодовая точка Юникода U+0061.

ВREPL можно создать двоичный код для буквы "a":

>>>letter_a = b'a'

>>>letter_a.decode('utf8')

'a'

Он правильно декодируется в UTF-8.

UTF-16 работает с кодовыми пунктами, содержащими от 2 до 4 байт. 1-бай- товое представление буквы "a" декодироваться не будет:

Книги для программистов: https://t.me/booksforits

276    Объекты и типы

>>> letter_a.decode('utf16') Traceback (most recent call last):

File "<stdin>", line 1, in <module> UnicodeDecodeError: 'utf-16-le' codec can't decode

byte 0x61 in position 0: truncated data

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

Широкие символы

Если вы работаете с входной строкой Юникода в неизвестной кодировке из исходного кода CPython, используйте тип C wchar_t.

wchar_t — стандарт C для строк в многобайтовой кодировке, хорошо подходящий для хранения строк Юникода в памяти. После PEP 393 тип wchar_t был выбран как формат хранения Юникода. Строковый объект Юникода предоставляет PyUnicode_FromWideChar() — вспомогательную функцию, которая преобразует константу wchar_t в объект строки.

Например, функция pymain_run_command(), используемая python -c, преобразует аргумент -c в строку Юникода:

Modules main.c, строка 226

static int

pymain_run_command(wchar_t *command, PyCompilerFlags *cf)

{

PyObject *unicode, *bytes; int ret;

unicode = PyUnicode_FromWideChar(command, -1);

Маркеры последовательности байтов

При декодировании ввода (например, файла) CPython может определить порядок байтов по маркеру последовательности байтов (BOM, byte order mark). BOM — специальные символы, располагающиеся в начале потока байтов Юникода. Они сообщают получателю порядок байтов, с помощью которого хранятся данные.

Разные компьютерные системы могут кодировать данные с разным порядком байтов. Если вы используете неправильный порядок (даже с правильной

Книги для программистов: https://t.me/booksforits

Тип строки Юникода    277

кодировкой), данные будут повреждены. При прямом порядке (Big-Endian) сначала передается старший, наиболее значимый байт. При обратном порядке (Little-Endian) на первое место ставится младший, наименее значимый байт.

Спецификация UTF-8 поддерживает маркеры BOM, но они ни на что не влияют. UTF-8 BOM может находиться в начале закодированной последовательности данных, представленной в виде b'\xef\xbb\xbf'; он сообщает Python, что поток данных с большой вероятностью закодирован в UTF-8. UTF-16 и UTF-32 поддерживают BOM для прямого и обратного порядка.

Порядок байтов по умолчанию в CPython задается глобальным значением sys.byteorder:

>>> import sys; print(sys.byteorder) little

Пакет encodings

Пакет encodings в Lib encodings включает более сотни встроенных поддерживаемых кодировок для CPython. Каждый раз, когда метод .encode() или

.decode() вызывается для обычной или байтовой строки, кодировка ищется в этом пакете.

Каждая кодировка определяется как отдельный модуль. Например, ISO2022_ JP — популярная кодировка для японских систем электронной почты — объявляется в Lib encodings iso2022_jp.py.

Каждый модуль кодировки определяет функцию getregentry() и регистрирует следующие характеристики:

zz уникальное имя;

zz функции кодирования и декодирования из модуля кодека; zz классы инкрементального кодера и декодера;

zz классы чтения и записи потоков данных.

Многие модули кодировок совместно используют одни и те же кодеки из модуля codecs или _mulitbytecodec. Некоторые модули кодировок используют отдельный модуль кодеков на C из файла Modules cjkcodecs.

Например, модуль кодировки ISO2022_JP импортирует модуль расширения C, _codecs_iso2022, из файла Modules cjkcodecs _codecs_iso2022.c:

Книги для программистов: https://t.me/booksforits

278    Объекты и типы

import _codecs_iso2022, codecs import _multibytecodec as mbc

codec = _codecs_iso2022.getcodec('iso2022_jp')

class Codec(codecs.Codec): encode = codec.encode decode = codec.decode

class IncrementalEncoder(mbc.MultibyteIncrementalEncoder, codecs.IncrementalEncoder):

codec = codec

class IncrementalDecoder(mbc.MultibyteIncrementalDecoder, codecs.IncrementalDecoder):

codec = codec

Пакет encodings также содержит модуль Lib encodings aliases.py, в котором есть словарь aliases. Этот словарь назначает кодировкам в реестре альтернативные имена. Например, utf8, utf-8 и u8 являются синонимами для кодировки utf_8.

Модуль codecs

Модуль codecs обеспечивает преобразование данных с заданной кодировкой. Функцию кодирования или декодирования для конкретной кодировки можно получить вызовом getencoder() или getdecoder() соответственно:

>>>iso2022_jp_encoder = codecs.getencoder('iso2022_jp')

>>>iso2022_jp_encoder('\u3072\u3068') # hi-to

(b'\x1b$B$R$H\x1b(B', 2)

Функция кодирования возвращает двоичный результат и количество байтов в выходных данных в виде кортежа. codecs также реализует встроенную функцию open() для открытия файловых дескрипторов операционной системы.

Реализации кодеков

Реализация объекта Юникода (Objects unicodeobject.c) содержит следующие методы кодирования.

Книги для программистов: https://t.me/booksforits

 

Тип строки Юникода    279

 

 

КОДЕК

КОДИРОВЩИК

ascii

PyUnicode_EncodeASCII()

latin1

PyUnicode_EncodeLatin1()

UTF7

PyUnicode_EncodeUTF7()

UTF8

PyUnicode_EncodeUTF8()

UTF16

PyUnicode_EncodeUTF16()

UTF32

PyUnicode_EncodeUTF32()

unicode_escape

PyUnicode_EncodeUnicodeEscape()

raw_unicode_escape

PyUnicode_EncodeRawUnicodeEscape()

Методы декодирования имеют похожие имена, но Encode заменяется на Decode.

Реализация других кодировок размещается в Modules _codecs, чтобы не загромождать реализацию основного объекта строки Юникода. Кодеки unicode_ escape и raw_unicode_escape используются во внутренней работе CPython.

Внутренние кодеки

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

сгенерированием исходного кода. Эти кодировки могут использоваться

слюбым текстовым вводом или выводом.

КОДЕК

НАЗНАЧЕНИЕ

idna

Реализует RFC 3490

mbcs

Кодирует в соответствии с кодовой страницей ANSI (толь-

 

ко на Windows)

raw_unicode_escape

Преобразует необработанные строковые литералы в ис-

 

ходном коде Python в строку

string_escape

Преобразует строковые литералы в исходном коде Python

 

в строку

undefined

Пробует применить установленную по умолчанию систем-

 

ную кодировку

Книги для программистов: https://t.me/booksforits

280    Объекты и типы

КОДЕК

НАЗНАЧЕНИЕ

unicode_escape

Преобразует строки в исходном коде Python в литерал

 

Юникода

unicode_internal

Возвращает внутреннее представление CPython

Также есть несколько чисто двоичных кодировок, которые должны использоваться с codecs.encode() или codecs.decode() с входной байтовой строкой, как в следующем примере:

>>> codecs.encode(b'hello world', 'base64') b'aGVsbG8gd29ybGQ=\n'

Список чисто двоичных кодировок:

КОДЕК

СИНОНИМЫ

НАЗНАЧЕНИЕ

base64_codec

base64, base-64

Преобразование в MIME base64

bz2_codec

bz2

Сжатие строки с использованием bz2

hex_codec

hex

Преобразование в шестнадцатеричное

 

 

представление с двумя цифрами на байт

quopri_codec

quoted-

Преобразование операнда в MIME Quoted

 

printable

Printable

rot_13

rot13

Возвращение результата подстановочного

 

 

шифрования Цезаря (сдвиг 13)

uu_codec

uu

Преобразование с использованием

 

 

uuencode

zlib_codec

zip, zlib

Сжатие с использованием gzip

Пример

Слот типа tp_richcompare заполняется PyUnicode_RichCompare() в PyUnicode_ Type. Эта функция сравнивает строки и может адаптироваться для использования оператора ~=. Поведение, которое мы реализуем, — сравнение двух строк без учета регистра символов.

Сначала добавьте дополнительный блок case, который будет проверять строки в левой и правой части на двоичную эквивалентность:

Книги для программистов: https://t.me/booksforits

Тип строки Юникода    281

Objects unicodeobject.c, строка 11361

PyObject *

PyUnicode_RichCompare(PyObject *left, PyObject *right, int op)

{

...

if (left == right) { switch (op) { case Py_EQ:

case Py_LE:

>>>case Py_AlE: case Py_GE:

/* Строка равна самой себе */

Py_RETURN_TRUE;

Затем добавьте новый блок else if для оператора Py_AlE. В нем будут выполняться следующие действия:

1.Преобразование левой строки в новую строку в верхнем регистре.

2.Преобразование правой строки в новую строку в верхнем регистре.

3.Сравнение двух строк.

4.Разыменование обеих временных строк для освобождения памяти.

5.Возвращение результата.

Код должен выглядеть примерно так:

else if (op == Py_EQ || op == Py_NE) {

...

}

/* Добавьте следующие строки */ else if (op == Py_AlE){

PyObject* upper_left = case_operation(left, do_upper); PyObject* upper_right = case_operation(right, do_upper); result = unicode_compare_eq(upper_left, upper_right); Py_DECREF(upper_left);

Py_DECREF(upper_right);

return PyBool_FromLong(result);

}

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

>>>"hello" ~= "HEllO"

True

>>>"hello?" ~= "hello"

False

Книги для программистов: https://t.me/booksforits