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

150    Цикл вычисления

Добавление отсутствующих именованных аргументов

Любые дополнительные именованные аргументы, переданные при вызове функции, добавляются в словарь **kwargs.

Если этот словарь не существует, выдается исключение.

Свертка замыканий

Все имена замыканий добавляются в список свободных имен переменных объекта кода.

Создание генераторов, сопрограмм и асинхронных генераторов

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

СМ. ТАКЖЕ

API и реализация генераторов, сопрограмм и асинхронных кадров рас­ сматриваются в главе «Параллелизм и конкурентность».

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

Наконец, с новым кадром вызывается _PyEval_EvalFrame().

ВЫПОЛНЕНИЕ КАДРА

Как упоминалось ранее в главах «Лексический анализ и парсинг с использованием синтаксических деревьев» и «Компилятор», объект кода содержит выполняемый байт-код, закодированный в двоичном формате. Также он содержит список переменных и таблицу символических имен.

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

Выполнение кадра    151

Локальные и глобальные переменные определяются во время выполнения в зависимости от того, как была вызвана функция, модуль или блок. Эта информация добавляется в кадр функцией_PyEval_EvalCode().

Существуют и другие варианты использования кадров — например, декоратор сопрограмм, который динамически генерирует кадр с целью в качестве переменной.

Функция открытого API PyEval_EvalFrameEx() вызывает функцию вычисления кадра, настроенную для интерпретатора, из свойства eval_frame. Предложение PEP 523 сделало вычисление кадра подключаемым (Python 3.7 и выше).

_PyEval_EvalFrameDefault() — установленная по умолчанию функция вычисления кадра, и это единственный вариант, входящий в комплект поставки CPython.

ПРИМЕЧАНИЕ

При чтении файла Python ceval.c можно заметить, сколько раз в нем ис­ пользуются макросы C.

Макросы C открывают возможность повторного использования кода без лишних затрат на вызов функций.Компилятор преобразует макросы в код C, а затем компилирует его.

После установки официального расширения C/C++ в Visual Studio Code будет выводиться подстановка макроса:

В CLion выделите макрос и нажмите Alt+пробел, чтобы просмотреть его определение.

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

152    Цикл вычисления

Эта центральная функция собирает все воедино и приводит в действие ваш код. В ней воплощены целые десятилетия оптимизации, так что даже одна строка кода может значительно повлиять на производительность CPython в целом.

Все, что выполняется в CPython, проходит через функцию вычисления кадра.

Трассировка выполнения кадра

В Python 3.7 и выше можно пошагово отслеживать выполнение кадров, установив атрибут трассировки для текущего потока. Тип PyFrameObject содержит свойство f_trace типа PyObject *. Предполагается, что значение указывает на функцию Python.

Следующий пример кода выбирает в качестве глобальной функции трассировки функцию с именем my_trace(), которая получает стек из текущего кадра, выводит дизассемблированные коды операций на экран и добавляет информацию для отладки:

cpython-book-samples 31 my_trace.py

import sys import dis import traceback import io

def my_trace(frame, event, args): frame.f_trace_opcodes = True

stack = traceback.extract_stack(frame) pad = " "*len(stack) + "|"

if event == "opcode":

with io.StringIO() as out:

dis.disco(frame.f_code, frame.f_lasti, file=out) lines = out.getvalue().split("\n") [print(f"{pad}{l}") for l in lines]

elif event == "call": print(f"{pad}Calling {frame.f_code}")

elif event == "return": print(f"{pad}Returning {args}")

elif event == "line":

print(f"{pad}Changing line to {frame.f_lineno}") else:

print(f"{pad}{frame} ({event} - {args})") print(f"{pad}----------------------------------") return my_trace

sys.settrace(my_trace)

# Выполнение кода для демонстрации eval('"-".join([letter for letter in "hello"])')

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

Стек значений    153

Функция sys.settrace() назначает переданную функцию функцией трассировки по умолчанию для текущего состояния потока. У всех новых кадров, созданных после этого вызова, поле f_trace инициализируется этой функцией.

Следующий фрагмент выводит код из каждого стека и указатель на следующую операцию перед ее выполнением. Если кадр возвращает значение, выводится команда return:

Полный список возможных инструкций байт-кода приведен в документации модуля dis1.

СТЕК ЗНАЧЕНИЙ

Внутри основного цикла вычисления создается стек значений. Стек содержит список указателей на экземпляры PyObject. Это могут быть переменные, ссылки на функции (которые в Python являются объектами) или любые другие объекты Python.

Инструкции байт-кода в цикле вычисления получают входные данные из стека значений.

Пример операции байт-кода: BINARY_OR

Бинарные операции, которые рассматривались в предшествующих главах, компилируются в одну инструкцию.

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

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

154    Цикл вычисления

Допустим, вы вставили в Python оператор or:

if left or right: pass

Компилятор преобразует оператор or в инструкцию BINARY_OR:

static int

binop(struct compiler *c, operator_ty op)

{

switch (op) { case Add:

return BINARY_ADD;

...

case BitOr:

return BINARY_OR;

В цикле вычисления секция case для BINARY_OR получает два значения из стека значений (левый и правый операнды), после чего вызывает PyNumber_Or для этих двух объектов:

...

case TARGET(BINARY_OR): {

PyObject *right = POP(); PyObject *left = TOP();

PyObject *res = PyNumber_Or(left, right); Py_DECREF(left);

Py_DECREF(right); SET_TOP(res);

if (res == NULL) goto error;

DISPATCH();

}

Результат (res) назначается вершиной стека, переопределяя текущее верхнее значение.

Моделирование стека значений

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

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

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

Стек значений    155

В CPython для добавления объектов в стек значений используется макрос PUSH(a), где a — указатель на PyObject.

Предположим, вы создали PyLong со значением 10 и занесли его в стек значений:

PyObject *a = PyLong_FromLong(10);

PUSH(a);

Это действие приводит к следующему эффекту:

 

 

 

 

В следующей операции для получения этого значения используется макрос POP()(получение верхнего значения стека):

PyObject *a = POP(); // a - PyLongObject со значением 10

Это действие возвращает верхнее значение, а стек значений остается пустым:

 

 

 

 

Допустим, в стек были добавлены два значения:

PyObject *a = PyLong_FromLong(10);

PyObject *b = PyLong_FromLong(20);

PUSH(a);

PUSH(b);

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

156    Цикл вычисления

Они сохраняются в стеке в порядке добавления, поэтому a попадет на вторую позицию в стеке:

b

 

b

 

 

 

 

При получении верхнего значения из стека вы получите указатель на b, потому что именно эта переменная сейчас находится на вершине:

PyObject *val = POP(); // Возвращает указатель на b

 

 

b

b

a

a

 

 

Если вам понадобится получить указатель на верхнее значение в стеке без его извлечения, можно воспользоваться операцией PEEK(v), где v — позиция стека:

PyObject *first = PEEK(0);

0 соответствует вершине стека, а 1 — второй позиции:

a

a

a

 

 

Для клонирования значения с вершины стека используется макрос DUP_TOP():

DUP_TOP();

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