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

Основная компиляция    121

VISIT_QUIT(st, 0);

if (s->v.FunctionDef.args->defaults)

VISIT_SEQ(st, expr, s->v.FunctionDef.args->defaults); if (s->v.FunctionDef.args->kw_defaults)

VISIT_SEQ_WITH_NULL(st, expr, s->v.FunctionDef.args->kw_defaults);

if (!symtable_visit_annotations(st, s, s->v.FunctionDef.args, s->v.FunctionDef.returns))

VISIT_QUIT(st, 0);

if (s->v.FunctionDef.decorator_list)

VISIT_SEQ(st, expr, s->v.FunctionDef.decorator_list); if (!symtable_enter_block(st, s->v.FunctionDef.name,

FunctionBlock, (void *)s, s->lineno, s->col_offset))

VISIT_QUIT(st, 0);

VISIT(st, arguments, s->v.FunctionDef.args); VISIT_SEQ(st, stmt, s->v.FunctionDef.body); if (!symtable_exit_block(st, s))

VISIT_QUIT(st, 0); break;

case ClassDef_kind: {

...

}

case Return_kind:

...

case Delete_kind:

...

case Assign_kind:

...

case AnnAssign_kind:

...

После того как таблица символических имен будет создана, она передается компилятору.

ОСНОВНАЯ КОМПИЛЯЦИЯ

Итак, теперь, когда у PyAST_CompileObject() имеется состояние компилятора, таблица символических имен и модуль в форме AST, можно начинать фактическую компиляцию.

Основной компилятор решает две задачи:

1.Преобразование состояния, таблицы символических имен и AST в граф потока управления (CFG).

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

122    Компилятор

2.Предупреждение возникновения исключений на стадии исполнения посредством перехвата логических ошибок и ошибок кода.

Обращение к компилятору из Python

Компилятор можно вызвать из кода Python при помощи встроенной функции compile(). Функция возвращает объект кода (code object):

>>>co = compile("b+1", "test.py", mode="eval")

>>>co

<code object <module> at 0x10f222780, file "test.py", line 1>

Как и в случае с symtable() API, простое выражение должно использовать режим (mode) "eval", а модуль, функция или класс — режим "exec".

Cкомпилированный код находится в свойстве co_code объекта кода:

>>> co.co_code b'e\x00d\x00\x17\x00S\x00'

Стандартная библиотека также включает модуль dis, дизассемблирующий инструкции байт-кода. Их можно вывести на экран или получить список экземпляров Instruction.

ПРИМЕЧАНИЕ

Тип Instruction в модуле dis является отражением типа instr из C API.

Если импортировать модуль dis и передать dis() свойство co_code объекта кода, то функция дизассемблирует его и выведет инструкции в REPL:

>>>import dis

>>>dis.dis(co.co_code)

0 LOAD_NAME 0 (0)

2 LOAD_CONST 0 (0)

4 BINARY_ADD

6 RETURN_VALUE

LOAD_NAME, LOAD_CONST, BINARY_ADD и RETURN_VALUE — инструкции байткода. Термин «байт-код» объясняется тем, что в двоичной форме длина

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

Основная компиляция    123

инструкций составляет 1 байт. Впрочем, начиная с Python 3.6, формат хранения был расширен до машинного слова1, так что формально это «слово-код», а не «байт-код».

Полый список инструкций байт-кода2 доступен для каждой версии Python, причем он изменяется между версиями. Например, в Python 3.7 появились новые инструкции байт-кода, ускоряющие вызов некоторых методов.

В предшествующих главах мы рассмотрели пакет instaviz. Его функциональность включает визуализацию типа объекта кода посредством запуска компилятора. Кроме того, instaviz выводит операции байт-кода внутри объектов кода.

Снова выполните instaviz, чтобы просмотреть объект кода и байт-код для функции, определенной в REPL:

>>>import instaviz

>>>def example(): a = 1

b = a + 1 return b

>>>instaviz.show(example)

API компилятора на C

Точка входа для компиляции модулей AST, compiler_mod(), переключается на разные функции компилятора в зависимости от типа модуля. Если в mod установлен тип Module, модуль компилируется в качестве единиц компиляции в свойство c_stack. Затем запускается функция assemble() для создания объекта PyCodeObject из стека единиц компиляции.

Возвращается новый объект кода, который передается на выполнение интерпретатором или кэшируется и сохраняется на диске в файле .pyc:

Python compile.c, строка 1820

static PyCodeObject *

compiler_mod(struct compiler *c, mod_ty mod)

{

1 2 байта. — Примеч. ред.

2 https://docs.python.org/3/library/dis.html#python-bytecode-instructions.

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

124    Компилятор

PyCodeObject *co; int addNone = 1;

static PyObject *module;

...

switch (mod->kind) { case Module_kind:

if (!compiler_body(c, mod->v.Module.body)) { compiler_exit_scope(c);

return 0;

}

break;

case Interactive_kind:

...

case Expression_kind:

...

...

co = assemble(c, addNone); compiler_exit_scope(c); return co;

}

compiler_body() перебирает все команды в модуле и посещает их:

Python compile.c, строка 1782

static int

compiler_body(struct compiler *c, asdl_seq *stmts)

{

int i = 0; stmt_ty st;

PyObject *docstring;

...

for (; i < asdl_seq_LEN(stmts); i++)

VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i)); return 1;

}

Тип команды определяется вызовом asdl_seq_GET(), который проверяет тип узла AST.

Через макрос VISIT вызывает функцию для каждого типа команды из

Python compile.c:

#define VISIT(C, TYPE, V) {\

if (!compiler_visit_ ## TYPE((C), (V))) \ return 0; \

}

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

Основная компиляция    125

Затем для stmt (общий тип команды) компилятор вызывает compiler_visit_ stmt(), после чего происходит переключение по всем возможным типам команд из Parser Python.asdl:

Python compile.c, строка 3375

static int

compiler_visit_stmt(struct compiler *c, stmt_ty s)

{

Py_ssize_t i, n;

/* Всегда присваивайте номер строки следующей инструкции для stmt */

SET_LOC(c, s); switch (s->kind) {

case FunctionDef_kind:

return compiler_function(c, s, 0); case ClassDef_kind:

return compiler_class(c, s);

...

case For_kind:

return compiler_for(c, s);

...

}

return 1;

}

Пример команды с for в Python:

for i in iterable:

# блок

else: # не обязательно, если iterable — False

# блок

Команду for можно наглядно представить на синтаксической диаграмме:

for

exprlist

in

testlist

 

:

TYPE_COMMENT

suite

else

:

suite

Если команда относится к типу for, то compiler_visit_stmt() вызывает compiler_for(). Эквивалентная функция compiler_*() существует для всех типов выражений и команд. Более тривиальные типы встраивают строки с инструкциями байт-кода, тогда как некоторые более сложные типы команд вызывают другие функции.

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

126    Компилятор

Инструкции

У многих команд есть подкоманды. Цикл for имеет тело, но при присваивании и в итераторе также могут использоваться составные выражения.

Компилятор передает блоки с последовательностью инструкций состоянию компилятора. Структура данных инструкций содержит код операции, аргументы, целевой блок (для инструкции перехода; об этом ниже), а также номер строки команды.

Тип инструкции

Тип инструкции instr содержит следующие поля.

ПОЛЕ

ТИП

НАЗНАЧЕНИЕ

i_jabs

unsigned

Флаг инструкции абсолютного перехода

i_jrel

unsigned

Флаг инструкции относительного перехода

i_lineno

int

Номер строки, для которой создается эта

 

 

инструкция

i_opcode

unsigned char

Код операции, представляемой инструкцией

 

 

(см. Include Opcode.h)

i_oparg

int

Аргумент кода операции

i_target

basicblock*

Указатель на блок перехода из основного блока

 

 

(basicblock), если значение i_jrel истинно

Инструкции перехода

Инструкции перехода позволяют «перепрыгнуть» из одной инструкции в другую. Они делятся на абсолютные (безусловные) и относительные (условные).

Инструкции абсолютного перехода задают точный номер инструкции в скомпилированном объекте кода, а инструкции относительного перехода — позицию перехода относительно другой инструкции.

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

Основная компиляция    127

Базовые блоки

Базовый блок (тип basicblock) содержит следующие поля.

ПОЛЕ

ТИП

НАЗНАЧЕНИЕ

b_ialloc

int

Длина массива инструкций (b_instr)

b_instr

instr *

Указатель на массив инструкций

b_iused

int

Количество использованных инструкций

 

 

(b_instr)

b_list

basicblock *

Список блоков в данной единице компиляции

 

 

(в обратном порядке)

b_next

basicblock*

Указатель на следующий блок при обычном пото-

 

 

ке управления

b_offset

int

Смещение инструкции для блока, вычисляемое

 

 

assemble_jump_offsets()

b_return

unsigned

Истинно, если вставлен код операции

 

 

RETURN_VALUE

b_seen

unsigned

Используется для выполнения поиска в глубину

 

 

по базовым блокам (см.«Ассемблер»)

b_startdepth

int

Глубина стека при входе в блок, вычисляемая

 

 

stackdepth()

Операции и аргументы

Разным типам операций требуются разные аргументы. Например, ADDOP_JREL и ADDOP_JABS представляют «операцию сложения с переходом к относительной позиции» и «операцию сложения с переходом к абсолютной позиции» соответственно.

Существуют и другие макросы: ADDOP_I вызывает функцию compiler_addop_i(), которая добавляет операцию с целочисленным аргументом. ADDOP_O вызывает функцию compiler_addop_o(), которая добавляет операцию с аргументом

PyObject.

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