- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода CPython
- •Что в исходном коде?
- •Настройка среды разработки
- •IDE или редактор?
- •Настройка Visual Studio
- •Настройка Visual Studio Code
- •Настройка Vim
- •Выводы
- •Компиляция CPython
- •Компиляция CPython на macOS
- •Компиляция CPython на Linux
- •Установка специализированной версии
- •Знакомство с Make
- •Make-цели CPython
- •Компиляция CPython на Windows
- •Профильная оптимизация
- •Выводы
- •Грамматика и язык Python
- •Спецификация языка Python
- •Генератор парсеров
- •Повторное генерирование грамматики
- •Выводы
- •Конфигурация и ввод
- •Конфигурация состояния
- •Структура данных конфигурации среды выполнения
- •Конфигурация сборки
- •Сборка модуля из входных данных
- •Выводы
- •Генерирование конкретного синтаксического дерева
- •Парсер/токенизатор CPython
- •Абстрактные синтаксические деревья
- •Важные термины
- •Пример: добавление оператора «почти равно»
- •Выводы
- •Компилятор
- •Исходные файлы
- •Важные термины
- •Создание экземпляра компилятора
- •Флаги будущей функциональности и флаги компилятора
- •Таблицы символических имен
- •Основная компиляция
- •Ассемблер
- •Создание объекта кода
- •Использование Instaviz для вывода объекта кода
- •Пример: реализация оператора «почти равно»
- •Выводы
- •Цикл вычисления
- •Исходные файлы
- •Важные термины
- •Построение состояния потока
- •Построение объектов кадров
- •Выполнение кадра
- •Стек значений
- •Пример: добавление элемента в список
- •Выводы
- •Управление памятью
- •Выделение памяти в C
- •Проектирование системы управления памятью Python
- •Аллокаторы памяти CPython
- •Область выделения объектной памяти и PyMem
- •Область выделения сырой памяти
- •Нестандартные области выделения памяти
- •Санитайзеры выделенной памяти
- •Арена памяти PyArena
- •Подсчет ссылок
- •Сборка мусора
- •Выводы
- •Параллелизм и конкурентность
- •Модели параллелизма и конкурентности
- •Структура процесса
- •Многопроцессорный параллелизм
- •Многопоточность
- •Асинхронное программирование
- •Генераторы
- •Сопрограммы
- •Асинхронные генераторы
- •Субинтерпретаторы
- •Выводы
- •Объекты и типы
- •Примеры этой главы
- •Встроенные типы
- •Типы объектов
- •Тип type
- •Типы bool и long
- •Тип строки Юникода
- •Словари
- •Выводы
- •Стандартная библиотека
- •Модули Python
- •Модули Python и C
- •Набор тестов
- •Запуск набора тестов в Windows
- •Запуск набора тестов в Linux или macOS
- •Флаги тестирования
- •Запуск конкретных тестов
- •Модули тестирования
- •Вспомогательные средства тестирования
- •Выводы
- •Отладка
- •Обработчик сбоев
- •Компиляция поддержки отладки
- •LLDB для macOS
- •Отладчик Visual Studio
- •Отладчик CLion
- •Выводы
- •Бенчмаркинг, профилирование и трассировка
- •Использование timeit для микробенчмарка
- •Использование набора тестов производительности Python
- •Профилирование кода Python с использованием cProfile
- •Выводы
- •Что дальше?
- •Создание расширений C для CPython
- •Улучшение приложений Python
- •Участие в проекте CPython
- •Дальнейшее обучение
- •Препроцессор C
- •Базовый синтаксис C
- •Выводы
- •Благодарности
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