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

114    Компилятор

zz В качестве арены выделения памяти компилятора присваивается та, которая использовалась интерпретатором. За дополнительной информацией о распределителях памяти обращайтесь к разделу «Специальные распределители памяти».

zz Все флаги будущей функциональности задаются перед компиляцией кода.

ФЛАГИ БУДУЩЕЙ ФУНКЦИОНАЛЬНОСТИ И ФЛАГИ КОМПИЛЯТОРА

Для включения функциональности внутри компилятора используются флаги двух типов: флаги будущей функциональности и флаги компилятора. Эти флаги могут задаваться в двух местах:

1.В конфигурации состояния, содержащей переменные среды и флаги командной строки.

2.В исходном коде модуля с использованием команд __future__.

За дополнительной информацией о конфигурации состояния обращайтесь к разделу «Конфигурация состояния» главы «Конфигурация и ввод».

Флаги будущей функциональности

Необходимость в флагах будущей функциональности возникает из-за синтаксиса или возможностей конкретного модуля. Например, в Python 3.7 появилась отложенная обработка аннотаций типов с использованием флага будущей функциональности annotations:

from __future__ import annotations

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

Флаги будущей функциональности в 3.9

В версии 3.9 все флаги будущей функциональности, кроме двух (annotations и barry_as_FLUFL), являются обязательными и активизируются автоматически:

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

 

Флаги будущей функциональности и флаги компилятора    115

 

 

ФЛАГ

НАЗНАЧЕНИЕ

absolute_import

Включение абсолютного импортирования (PEP 328)

annotations

Отложенная обработка аннотаций типов (PEP 563)

barry_as_FLUFL

Пасхальное яйцо (PEP 401)

division

Использование оператора истинного деления (PEP 238)

generator_stop

Возможность использования StopIteration внутри генераторов

 

(PEP 479)

generators

Включение простых генераторов (PEP 255)

nested_scopes

Добавление статических вложенных областей видимости

 

(PEP 227)

print_function

Использование print как функции (3105)

unicode_literals

Хранение литералов str в Юникоде вместо байтов (PEP 3112)

with_statement

Включение оператора with (PEP 343)

ПРИМЕЧАНИЕ

Большинство флагов __future__ использовалось для обеспечения пор­ тируемости между Python 2 и 3. Вероятно, с приближением выхода Python 4.0 появится больше флагов будущей функциональности.

Флаги компилятора

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

Один из примеров флагов компилятора — флаг -O1 для оптимизации команд assert. Этот флаг отключает все команды assert, которые могли быть вставлены в код в целях отладки2. Режим также можно включить установкой переменной среды PYTHONOPTIMIZE=1.

1 https://docs.python.org/3/using/cmdline.html#cmdoption-o.

2 https://realpython.com/python-debugging-pdb/.

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

116    Компилятор

ТАБЛИЦЫ СИМВОЛИЧЕСКИХ ИМЕН

Перед компиляцией кода создается таблица символических имен, для чего используется функция API PySymtable_BuildObject().

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

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

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

ФАЙЛ

НАЗНАЧЕНИЕ

Python symtable.c

Реализация таблицы символических имен

Include symtable.h

Определение API таблицы символических имен и типов

Lib symtable.py

Модуль стандартной библиотеки symtable

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

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

Например, если вы создали метод с именем resolve_names() в одном классе, а потом объявили еще один метод с тем же именем в другом классе, необходимо точно указать, какой из методов будет вызываться внутри модуля.

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

Структура таблицы символических имен symtable содержит следующие поля.

ПОЛЕ

ТИП

НАЗНАЧЕНИЕ

recursion_depth

int

Текущая глубина рекурсии

recursion_limit

int

Предельная глубина рекурсии до выдачи

 

 

ошибки RecursionError

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

 

 

Таблицы символических имен    117

 

 

 

ПОЛЕ

ТИП

НАЗНАЧЕНИЕ

st_blocks

PyObject * (dict)

Соответствие адресов узлов AST и элемен-

 

 

тов таблицы символических имен

st_cur

_symtable_entry

Текущий элемент таблицы символических

 

 

имен

st_filename

PyObject * (str)

Имя компилируемого файла

st_future

PyFutureFeatures

Возможности будущей функциональности

 

 

модуля, влияющие на таблицу символиче-

 

 

ских имен

st_global

PyObject * (dict)

Ссылки на символические имена в st_top

st_nblocks

int

Количество использованных блоков

st_private

PyObject * (str)

Имя текущего класса (не обязательно)

st_stack

PyObject * (list)

Стек с информацией о пространствах имен

st_top

_symtable_entry

Элемент таблицы символических имен для

 

 

модуля

Использование модуля стандартной библиотеки symtable

Доступ к некоторым функциям C API таблицы символических имен предоставляется в Python через модуль symtable стандартной библиотеки.

При помощи другого модуля tabulate (доступного в PyPI) можно создать скрипт для вывода таблицы символических имен.

Таблицы символических имен могут быть вложенными. Если модуль содержит функцию или класс, они тоже будут иметь таблицу символических имен.

Создайте скрипт symviz.py с рекурсивной функцией show():

cpython-book-samples 30 symviz.py

import tabulate import symtable

code = """

def calc_pow(a, b): return a ** b

a = 1

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

118    Компилятор

b = 2

c = calc_pow(a,b)

"""

_st = symtable.symtable(code, "example.py", "exec")

def show(table):

print("Symtable {0} ({1})".format(table.get_name(), table.get_type()))

print(

tabulate.tabulate(

[

(

symbol.get_name(), symbol.is_global(), symbol.is_local(), symbol.get_namespaces(),

)

for symbol in table.get_symbols()

],

headers=["name", "global", "local", "namespaces"], tablefmt="grid",

)

)

if table.has_children():

[show(child) for child in table.get_children()] show(_st)

Запустите symviz.py в командной строке, чтобы просмотреть таблицу символических имен для примера:

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

Таблицы символических имен    119

Реализация таблицы символических имен

Реализация таблицы символических имен находится в файле Python symtable.c, а первичным интерфейсом является функция PySymtable_BuildObject().

По аналогии с компиляцией AST, рассмотренной в предыдущей главе, PySymtable_BuildObject() переключается между возможными типами mod_ty

(Module, Interactive, Expression и FunctionType) и обходит все содержащиеся в них команды.

Таблица символических имен в рекурсивном режиме анализирует узлы и ветви AST (типа mod_ty) и добавляет элементы в symtable:

Python symtable.c, строка 261

struct symtable *

PySymtable_BuildObject(mod_ty mod, PyObject *filename, PyFutureFeatures *future)

{

struct symtable *st = symtable_new(); asdl_seq *seq;

int i;

PyThreadState *tstate;

int recursion_limit = Py_GetRecursionLimit();

...

st->st_top = st->st_cur; switch (mod->kind) { case Module_kind:

seq = mod->v.Module.body;

for (i = 0; i < asdl_seq_LEN(seq); i++) if (!symtable_visit_stmt(st,

(stmt_ty)asdl_seq_GET(seq, i))) goto error;

break;

case Expression_kind:

...

case Interactive_kind:

...

case FunctionType_kind:

...

}

...

}

PySymtable_BuildObject() перебирает все команды модуля и вызывает symtable_visit_stmt() — по сути, огромную команду switch с условием case для каждого типа команд (определяемого в Parser Python.asdl).

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

120    Компилятор

У каждого типа команд имеется соответствующая функция для разрешения символических имен. Например, для команды типа определения функции (FunctionDef_kind) реализована конкретная логика для следующих действий:

zz проверка текущей глубины рекурсии и сравнение ее с предельной глубиной;

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

zz разрешение не литеральных аргументов по умолчанию по таблице символических имен;

zz разрешение аннотаций типа;

zz разрешение декораторов функций.

Наконец, symtable_enter_block() посещает блок с содержимым функции. Далее происходит посещение и преобразование аргументов, после чего посещается и обрабатывается тело функции.

ВАЖНО

У вас когда-либо возникал вопрос, почему аргументы по умолчанию в Python изменяемы? Все дело в symtable_visit_stmt().Аргумент по умол­ чанию представляет собой ссылку на переменную из symtable.

Такой подход позволяет избежать лишней работы по копированию зна­ чений в неизменяемый тип.

Чтобы вы лучше поняли суть происходящего, ниже приведен код C для построения таблицы символических имен для функции из symtable_visit_stmt().

Python symtable.c, строка 1171

static int

symtable_visit_stmt(struct symtable *st, stmt_ty s)

{

if (++st->recursion_depth > st->recursion_limit) { PyErr_SetString(PyExc_RecursionError,

"maximum recursion depth exceeded during compilation"); VISIT_QUIT(st, 0);

}

switch (s->kind) { case FunctionDef_kind:

if (!symtable_add_def(st, s->v.FunctionDef.name, DEF_LOCAL))

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