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

Пример: добавление элемента в список    157

Операция копирует значение с вершины стека, создавая два указателя на один объект:

 

 

 

 

 

 

Макрос ROT_TWO меняет местами первое и второе значения:

ROT_TWO();

Операция переставляет первое значение на место второго и наоборот:

 

b

b

 

 

 

Эффект cтека

Каждый код операции имеет заранее определенный эффект стека, вычисляемый вызовом stack_effect() из файла Python compile.c. Функция возвращает дельту в количестве значений в стеке для каждого кода операции.

Эффект стека может иметь положительное, отрицательное или нулевое значение. Если после выполнения операции эффект стека (например, +1) не равен дельте в стеке значений, выдается исключение.

ПРИМЕР: ДОБАВЛЕНИЕ ЭЛЕМЕНТА В СПИСОК

В Python доступен метод append()на созданном объекте списка:

my_list = [] my_list.append(obj)

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

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

Вэтом примере obj — объект, который добавляется в конец списка.

Вэтой операции задействованы две другие операции:

1.LOAD_FAST для загрузки obj на вершину стека значений из списка locals в кадре.

2.LIST_APPEND для добавления объекта.

LOAD_FAST состоит из пяти шагов:

1.Указатель на obj загружается из GETLOCAL(), при этом аргументом операции является загружаемая переменная. Список указателей на переменные хранится в fastlocals — копии атрибута PyFrame из f_localsplus. Аргумент операции — число, ссылающееся на индекс в массиве указателей fastlocals. Это означает, что Python загружает локальную переменную как копию указателя (вместо того, чтобы проводить поиск по имени переменной).

2.Если переменная более не существует, выдается исключение несвязан-

ной локальной переменной (UnboundLocalError).

3.Счетчик ссылок для значения (в данном случае obj) увеличивается на 1.

4.Указатель на obj помещается на вершину стека значений.

5.Вызывается макрос FAST_DISPATCH. Если трассировка включена, то цикл выполняется снова с полной трассировкой. Если трассировка отключена, для fast_next_opcode вызывается goto. Операция goto осуществляет переход к началу цикла за следующей инструкцией.

Пять шагов LOAD_FAST:

...

case TARGET(LOAD_FAST): {

 

PyObject *value = GETLOCAL(oparg);

// 1.

if (value == NULL) {

 

format_exc_check_arg(

 

PyExc_UnboundLocalError,

 

UNBOUNDLOCAL_ERROR_MSG,

 

PyTuple_GetItem(co->co_varnames, oparg));

 

goto error;

// 2.

}

 

Py_INCREF(value);

// 3.

PUSH(value);

// 4.

FAST_DISPATCH();

// 5.

}

 

...

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

Пример: добавление элемента в список    159

Указатель на obj теперь находится на вершине стека значений, и выполняется следующая инструкция LIST_APPEND.

Многие операции байт-кода ссылаются на базовые типы, такие как PyUnicode или PyNumber. Например, LIST_APPEND добавляет объект в конец списка. Для этого он извлекает указатель из стека значений и возвращает указатель на последний объект в стеке.

Макрос является сокращением для следующего кода:

PyObject *v = (*--stack_pointer);

Теперь указатель на obj хранится как v. Указатель списка загружается из

PEEK(oparg).

Затем вызывается функция C API для списков Python для list и v. Код находится в файле Objects listobject.c. Он будет рассматриваться в главе «Объекты и типы».

Далее происходит вызов PREDICT, который прогнозирует, что следующей операцией будет JUMP_ABSOLUTE. Макрос PREDICT содержит сгенерированные компилятором команды goto для секций case всех потенциальных операций.

Это означает, что процессор может перейти к этой инструкции без повторного прохождения цикла:

...

case TARGET(LIST_APPEND): {

PyObject *v = POP(); PyObject *list = PEEK(oparg); int err;

err = PyList_Append(list, v); Py_DECREF(v);

if (err != 0) goto error;

PREDICT(JUMP_ABSOLUTE);

DISPATCH();

}

...

Некоторые операции, такие как CALL_FUNCTION и CALL_METHOD, содержат аргумент операции, который ссылается на другую скомпилированную функцию. В этом случае другой кадр помещается в стек кадров в потоке, и цикл вычисления выполняется для этой функции до ее завершения.

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

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

ПРИМЕЧАНИЕ

Некоторыекодыоперацийобразуютпары,чтопозволяетпрогнозировать второй код при выполнении первого.Например,за COMPARE_OP часто следует POP_JUMP_IF_FALSE или POP_JUMP_IF_TRUE.

Если вы ведете статистику кодов операций, у вас два варианта:

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

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

Прогнозирование кодов операций отключается в потоковом коде, так как последний позволяет ЦП регистрировать предсказания ветвлений раздельно для каждого кода операции.

Каждый раз, когда новый кадр создается и заносится в стек, в поле f_back кадра устанавливается текущий кадр, прежде чем будет создан новый. Такое вложение кадров становится очевидным, если посмотреть на трассировку стека:

cpython-book-samples 31 example_stack.py

def function2(): raise RuntimeError

def function1(): function2()

if __name__ == "__main__": function1()

При выполнении этого кода в командной строке вы получите следующий результат:

$ ./python example_stack.py

Traceback (most recent call last):

File "example_stack.py", line 8, in <module> function1()

File "example_stack.py", line 5, in function1 function2()

File "example_stack.py", line 2, in function2 raise RuntimeError

RuntimeError

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