Глава 8. Цикл расчёта
Содержание
- Глава 8. Цикл расчёта
- Относящиеся к делу исходные файлы
- Важные термины
- Построение состояния потока
- Построение объектов кадров
- Тип объекта кадра
- Относящиеся к делу исходные файлы
- API инициализации объекта кадра
- Преобразование параметров ключевых слов в словарь
- Преобразование позиционных аргументов в переменные
- Упаковка позиционных аргументов в
*args
- Загрузка аргументов ключевых слов
- Добавление пропущенных позиционных аргументов
- Добавление пропущенных аргументов ключевых слов
- Свёртывание замыканий
- Создание генераторов, сопрограмм и асинхронных генераторов
- Исполнение кадра
- Стек значений
- Пример: Добавление элемента в список
- Выводы
До сих пор вы наблюдали как выполняется синтаксический разбор кода Python в дерево абстрактного синтаксиса и оно компилируется в объекты кода. Эти кодовые объекты содержат списки дискретных операций в форме байтового кода.
Существует один важный пропущенный момент для этих кодовых объектов для исполнения и приведения их в действие: им требуются входные данные. В Python эти входные данные получаются из локальных или глобальных переменных.
В этой главе вам будет представлено введение в понятие с названием стека значений, при котором переменные создаются, изменяются и применяются операциями байтового кода в скомпилированных вами объектах кода.
Выполнение кода в CPython происходит внутри центрального цикла с названием цикла
расчёта (evaluation loop). Интерпретатор CPython будет рассчитывать и выполнять кодовый объект, выбираемый из либо выстроенного
файла .pyc
или из самого компилятора:
В цикле расчёта каждая из инструкций байтового кода берётся и исполняется при помощи стековой системы на основе кадров.
Замечание | |
---|---|
Кадры стека это применяемый многими средами времени выполнения тип данных, не только Python. Стековые кадры позволяют вызов функций и возврат переменных между функциями. Кадры стека также содержат аргументы, локальные переменные, также прочие сведения с учётом состояния. Кадр стека существует для каждого вызова функции и они последовательно выстраиваются в стек. Вы можете наблюдать стек кадра CPython всякий раз, когда не обрабатывается исключительная ситуация:
|
Вот те исходные файлы, которые относятся к циклу расчёта:
Файл | Назначение |
---|---|
|
Реалиация ядра цикла вычислений |
|
Определение GIL и алгоритма управления |
Вот некоторые важные термины, которыми вы будете пользоваться в этой главе:
-
Цикл вычислений будет получать объект кода и преобразовывать его в последовательность объектов кадров.
-
Сам интерпертатор обладает по крайней мере одним потоком.
-
Каждый поток имеет состояние потока
-
Кадровые объекты выполняются в стеке, носящем название стека кадров.
-
Стек значений содержит ссылки на переменные.
Прежде чем кадр сможет исполняться, ему требуется присоединиться к некому потоку. CPython способен обладать большим числом потоков, запускаемых в любое время внутри единого интерпретатора. Состояние интерпретатора содержит присоединённый список таких потоков.
CPython всегда обладает хотя бы одним потоком, причём каждый поток обладает своим собственным состоянием.
Смотри также | |
---|---|
Более подробно построение потоков обсуждается в Главе 10, Параллельность и одновременность. |
Тип состояния потока обладает тридцатью свойствами, включая следующие:
-
Уникальный идентификатор
-
Присоединённый список на прочие состояния потока
-
Состояние интерпертатора, из которого о был порождён
-
Исполняемый в данный момент кадр
-
Текущая глубина рекурсии
-
Не обязательные функции отслеживания
-
Обрабатываемая прямо сейчас исключительная ситуация
-
Все подлежащие в данный момент асинхронные исключительные ситуации
-
Некий стек исключительных ситуаций, возбуждённых при возведении множественных исключительных ситуаций (например, внутри блока
except
) -
Счётчик GIL
-
Асинхронный генератор счётчиков
Относящиеся к состоянию потока исходные файлы разбросаны по множеству файлов:
Файл | Назначение |
---|---|
|
Реализация API потока |
|
Некоторые API состояния и определения типов |
|
API состояния интерпретатора и определения типов |
|
API построения потоков |
|
Некоторые API состояния и потока и интерпретатора |
Скомпилированные объекты кодов вставляются в объекты кадров. Объекты кадров являются типом Python, а потому на них можно ссылаться как из C, так и из Python.
Объекты кадров также содержат прочие данные времени исполнения, необходимые для исполнения всех инструкций в соответствующих кодовых объектах. Эти данные содержат локальные переменные, глобальные переменные и встроенные модули.
Типом объекта кадра выступает PyObject
со следующими дополнительными свойствами:
Поле | Тип | Назначение |
---|---|---|
|
|
Указатель на предыдущий кадр в стеке или |
|
|
Последовательность блоков |
|
|
Символьная таблица для модуля |
|
|
Подлежащий исполнению кодовый объект |
|
|
Флаг того исполняется ли всё ещё данный кадр |
|
|
Заимствованная ссылка на генератор, или |
|
|
Глобальная символьная таблица ( |
|
|
Индекс этого кадра в |
|
|
Последняя инструкция, если она вызывается |
|
|
Текущий номер строки |
|
|
Локальная символьная таблица (все соответствия) |
|
|
Соединение |
|
|
Следующий свободный слот в |
|
|
Указатель на некую индивидуальную функцию отслеживания (см. раздел Отслеживание выполнения кадра) |
|
|
Включает пользовательскую функцию отслеживания для трассировки на уровне строки |
|
|
Включает пользовательскую функцию отслеживания для трассировки на уровне кода операции |
|
|
Указатель на самый последний локальный элемент |
Вот те исходные файлы, которые относятся к объектам кадра:
Файл | Назначение |
---|---|
|
Реалиация и API Python кадра объекта |
|
API объекта кадра и определение типа |
API для инициализации объекта кадра, PyEval_EvalCode(), выступает точкой входа для вычисления объекта кода.
PyEval_EvalCode()
является обёрткой вокруг внутренней функции _PyEval\_EvalCode()
.
Замечание | |
---|---|
PyEval_EvalCode() это сложная функция, которая определяет большое число поведений как объектов кадров, так и самого цикла интерпретатора. Именно она очень важна для понимания, ибо она также способна обучить вас некоторым принципам проектирования интерпретатора CPython. |
В этм разделе вы пошагово пройдёте логикой _PyEval\_EvalCode()
.
PyEval_EvalCode() определяет большое число аргументов:
-
tstate
:PyThreadState *
указывает на состояние потока самого потока данного кода, который будет вычисляться -
_co
:PyCodeObject*
содержит тот код, который должен быть помещён в объект кадра -
globals
:PyObject* (dict)
с названиями ключей в качестве ключей и их значениями -
locals
:PyObject* (dict)
с названиями ключей в качестве ключей и их значениями
Замечание | |
---|---|
В Python локальные и глобальные переменные хранятся в каче тве словарей. Вы можете получать доступ к этим словарям при помощи
встроенных функций
|
Все прочие аргументы не обязательные и не применяются в базовом API:
-
argcount
: Значение числа позиционных аргументов -
args
:PyObject* (tuple)
с со значениями позиционных аргументов по порядку -
closure
: Кортеж со строками для слияния с полемco_freevars
соответствующего кодового объекта -
defcount
: Значение длины установленных по умолчанию значений для позиционных аргументов -
defs
: Перечень значений по умолчанию для позиционных аргументов -
kwargs
: Список значений аргументов кодового слова -
kwcount
: Значение числа аргументов кодового слова -
kwdefs
: Словарь со значниями по умолчанию для аргументов кодового слова -
kwnames
: Список имён аргументов кодового слова -
name
: Название для данного вычисляемого предложения в виде строки -
qualname
: Полностью определённое название для данного вычисляемого предложения в виде строки
Вызов _PyFrame_New_NoTrack()
создаёт новый кадр. Этот API также доступен из API C через
PyFrame_New().
_PyFrame_New_NoTrack()
создаст новый PyFrameObject
следуя таким шагам:
-
Установит значение свойства
f_back
равным состоянию потока последнего кадра. -
Загрузит необходимые текущие встроенные функции через установку свойства
f_builtins
и загрузит сам модульbuiltins
при помощи PyModule_GetDict(). -
Установит значение свойства
f_code
на подлежащий вычислению кодовый блок. -
Установит значение свойства
f_valuestack
на некий пустой стек значений -
Установит значение указателя
f_stacktop
наf_valuestack
-
Установит значение глобального свойства,
f_globals
, на значение аргументаglobals
-
Установит значение локального свойства,
f_locals
, на некий новый словарь -
Установит значение
f_lineno
равным свойствуco_firstlineno
кодового объекта с тем, чтобы трассировщик содержал номера строк -
Установит значения всех остающихся свойств в их значения по умолчанию
Обладая новым экземпляром PyFrameObject
, могут быть построены все аргументы для его кодового объекта:
Определения функций могут содержать вместилище всего **kwargs
в качестве аргументов ключевых слов:
def example(arg, arg2=None, **kwargs):
print(kwargs["x"], kwargs["y"]) # resolves to a dictionary key
example(1, x=2, y=3) # 2 3
В такой ситуации создаётся некий новый словарь и выполняется повсеместное копирование не подвергшихся разрешению аргументов. Имя
kwargs
затем устанавливается в качестве локальной сферы действий данного кадра.
Все имеющиеся позиционные аргументы (если они предоставлены) устанавливаются в качестве локалных переменных. В Python аргументы функций уже локальные переменные внутри тела этой функции. Когда определён со значением некий позиционный аргумент, он доступен внутри сферы действий этой функции:
def example(arg1, arg2):
print(arg1, arg2)
example(1, 2) # 1 2
Счётчик ссылок инкрементально увеличивается для этих переменных, поэтому имеющийся сборщик мусора не сможет удалить их пока вычисляется данный кадр, например, пока эта функция не будет завершена и не вернёт результат.
Как и в случае с **kwargs
, аргумент функции с предшествующей ему *
может быть установлен в захват всех остающихся позиционных аргументов. Этот аргумент является кортежем, а имя *args
устанавливается в качестве локальной переменной:
def example(arg, *args):
print(arg, args[0], args[1])
example(1, 2, 3) # 1 2 3
Когда некая функция вызывается при помощи аргументов с ключевыми словами и значениями, словарь заполняется всеми остающимися аргументами ключевых слов, переданных вызывающей стороной, для которых не выполняется разрешение в именованные аргументы или позиционные аргументы.
Например, когда аргумент e
является ни позиционным, не именованным, он добавляется в
**remaining
:
>>> def my_function(a, b, c=None, d=None, **remaining):
print(a, b, c, d, remaining)
>>> my_function(a=1, b=2, c=3, d=4, e=5)
(1, 2, 3, 4, {"e": 5})
Замечание | |
---|---|
Исключительно позиционные аргументы (Positional-only arguments) являются новой функциональной возможностью в Python 3.8. Введённые в PEP 570, исключительно позиционные аргументы представляют собой способ прекращения пользователями вашего API применения позиционных аргументов в синтаксисе ключевых слов. Например, эта простая функция преобразовывает градусы по Фаренгейту в градусы по Цельсию. Обратите внимание на использование прямого
слэша (
Все аргументы слева от
Вызов этой функции с применением аргумента с ключевым словом для исключительно позиционного аргумента возбудит
|
Собственно разрешение значений словаря аргументов ключевых слов происходит после распаковки всех аргументов. PEP 570 исключительно позиционные аргументы отображаются запуском
цикла аргументов ключевых слов начиная с co_posonlyargcount
. Если символ
/
применялся в качестве третьего аргумента, тогда значением
co_posonlyargcount
будет равен 2
.
PyDict_SetItem()
вызывается для каждого из оставшихся аргументов для их добавления в словарь locals
. После его
исполнения все аргументы ключевых слов войдут в сферу действия локальных переменных.
Когда аргумент ключевого слова определяется с неким значением, тогда и оно доступно внутри этой сферы:
def example(arg1, arg2, example_kwarg=None):
print(example_kwarg) # example_kwarg is already a local variable.
Все предоставляемые вызову функции позиционные аргументы, которые не находятся в списке позиционных аргументов добавляются в кортеж
*args
. Если такой кортеж не существует, тогда возбуждается исключительная ситуация.
Все пердоставляемые вызову функции аргументы ключевых слов, не находящиеся в списке именованных аргументов ключевых слов добавляются в
словарь **kwargs
. Если такой словарь не существует, тогда возбуждается исключительная ситуация.
Все названия замыканий добавляются в список свободных имён переменных объекта кода.
Когда вычисляемый объект кода обладает флагом того, что он является генератором, сопрограммой или асинхронным генератором, тогда создаётся новый кадр с использованием одного из имеющихся уникальных методов в библиотеках генератора, сопрограммы или асинхронной библиотеки, а текущий кадр добавляется в качестве свойства.
Смотри также | |
---|---|
API и реализация кадров генераторов, сопрограмм и асинхронных кадров обсуждаются в Главе 10, Параллельность и одновременность. |
Такой новый кадр затем возвращается, а его первоначальный кадр не вычисляется. Это кадр вычисляется только когда вызывается для исполнения в качестве его цели соответствующий генератор, сопрограмма или асинхронный метод.
Наконец, с новым кадром вызывается _PyEval_EvalFrame().
Как уже обсуждалось ранее в Главе 6, Лексический и синтаксический разбор при помощи синтаксических деревьев и в Главе 7, Собственно компилятор объект кода содержит бинарное кодирование подлежащих исполнению байтовых кодов. Он также содержит перечень переменных и и символическую таблицу.
Значения локальных и глобальных переменных определяются во время исполнения на основании того как вызывались функции, модуль или блок. Эти сведения добавляются в соответствующий кадр через _PyEval_EvalCode().
Имеются и иные применения кадров, такие как декоратор сопрограммы, который динамически вырабатывает некий кадр с соответствующей целью в качестве переменной.
Общедоступный API, PyEval_EvalFrameEx(), вызывает настроенный интерпретатором кадр вычисляемой функции в свойстве
eval_frame
. Вычисление кадра было сделано подключаемым в Python 3.7 через
PEP 523.
_PyEval_EvalFrameDefault() является установленной по умолчанию функцией вычисления и единственным поставляемым с CPython вариантом.
Эта центральная функция объединяет всё воедино и воспроизводит ваш код к жизни. Она содержит десятилетия оптимизации, ибо даже одна строка кода способна оказать значительное воздействие на на производительность CPython в целом.
Всё что подлежит исполнению через CPython проходит через эту функцию вычисления.
Замечание | |
---|---|
Некоторые из вас при чтении Макросы C это некий способ обладания повторно применяемым кодом без накладных расходов выполнения вызова функции.. В Visual Studio Code идущие по ходу дела расширения макро показываются после того как вы установили официальное расширение C/C++: В CLion выберите макро и нажмите |
Вы можете пошагово проследовать за исполнением 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)
# Run some code for a demo
eval('"-".join([letter for letter in "hello"])')
sys.settrace()
установит функцией отслеживания по умолчанию текущего состояния потока ту, которая
предоставлена. Все создаваемые после этого вызова кадры будут обладать f_trace
, установленной в эту
функцию.
Этот фрагмент кода выводит на печать сам код внутри каждого стека и перед тем как выполнить её, указывает следующую операцию. После того как кадр возвращает значение, выводится на печать это возвращаемое предложение:
Полный перечень возможных инструкций байтового кода доступен в документации модуля dis.
Внутри ядра цикла вычисления создаётся стек значений. Этот стек является списком указателей на экземпляры PyObject
.
Они могут быть такими значениями как переменные, ссылки на функции (которые являются объектами Python) или иные прочие объекты Python.
Инструкции байтового кода в цикле вычислений будут получать входные данные из этого стека значений.
Все двоичные операции, которые вы изучали в предыдущих главах, компилируются в некую отдельную инструкцию.
Например, допустим мы вставили некую операцию or
в Python:
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;
В нашем цикле вычислений вариант для BINARY_OR
получит два значения из стека значений, операции
left
и right
, а затем вызовет
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
, затем устанавливается вершиной этого стека, перекрывая текущее значение
вершины.
Чтобы понять цикл вычислений, вам придётся разобраться c его стеком значений.
Один из способов представлять себе стек значений это рассматривать его как деревянный колышек, на который вы можете штабелировать цилиндры. При таком подходе вы могли бы добавлять или удалять лишь по одному цилиндру за раз и всегда только в его вершину или с неё.
В CPython вы можете вы можете добавлять объекты в стек значений при помощи макро PUSH(a)
, где
a
это указатель на PyObject
.
Например, допустим, вы создали PyLong
со значением 10
и
поместите его в свой стек значений:
PyObject *a = PyLong_FromLong(10);
PUSH(a);
Это действие имело бы такой эффект:
В своей следующей операции, для выборки этого значения, вы бы воспользовались макро POP()
для получения
значения из вершины стека:
PyObject *a = POP(); // a is PyLongObject with a value of 10
Это действие вернёт верхнее значение и завершится опустошением стека:
Теперь, допустим, вы добавили в стек два значения:
yObject *a = PyLong_FromLong(10);
PyObject *b = PyLong_FromLong(20);
PUSH(a);
PUSH(b);
Это бы завершилось в порядке такого добавления, а потому a
оказалось бы помещённым на второй позиции в
стеке:
Если бы вы выполнили выборку с вершины стека, вы бы получили указатель на b
, потому что он
находился в вершине:
PyObject *val = POP(); // returns ptr to b
Если вам требуется выбрать значение указателя из стека без его выталкивания, тогда вы можете воспользоваться операцией
PEEK(v)
, где v
это значение позиции в стеке:
PyObject *val = POP(); // returns ptr to b
0
представляет вершину стека, 1
будет представлять вторую
позицию:
Для клонирования своего значения в вершине стека вы можете применять макро DUP_TOP()
:
DUP_TOP();
Это действие скопирует значение в вершине для формирования двух указателей на один и тот же объект:
Макро обращения ROT_TWO
поменяет местами первое и второе значения:
ROT_TWO();
Это действие переключит порядок первого и второго значений:
Каждая из этих кодовых операций обладает предопределённым воздействием на стек,
вычисляемым stack_effect()
внутри Python/compile.c
.
Эта функция возвращает значение приращения в числе значений внутри стека значений для каждого кода операции.
Действия стека обладают положительным, отрицательным или нулевым значением. После того как операция осуществлена, если произошедшее
воздействие на стек (например, +1
) не соответствует значению приращения в стеке значений, возбуждается
исключительная ситуация.
В Python, когда вы создаёте некий список, в этом перечне объектов доступен метод append()
:
my_list = []
my_list.append(obj)
В данном примере obj
выступает неким объектом, который вы желаете добавить в конец своего списка.
В это действие вовлечены две операции:
-
LOAD_FAST
для загрузкиobj
в вершину стека значений из спискаlocals
данного кадра -
LIST_APPEND
для добавления этого объекта
В LOAD_FAST
вовлечено пять шагов:
-
Указатель на
obj
загружается изGETLOCAL()
, где значение загружаемой переменной это аргумент операции. Весь список указателей на переменные хранится вfastlocals
, которая является копией атрибутаPyFrame
изf_localsplus
. Аргументом этой операции выступает число указаний на значение индекса в самом массиве указателейfastlocals
. Это означает, что Python загружает локальные элементы как копии самих указателей, вместо того чтобы принимать во внимание значения имён переменных. -
Если такая переменная более не существует, возбуждается ошибочная ситуация несвязанной локальной переменной.
-
Значения счётчика ссылок для
value
(в нашем случае дляobj
) увеличиваются на единицу. -
Значение указателя на
obj
помещается в вершину стека значений. -
Вызывается макро
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.
}
...
Наш указатель на obj
теперь в вершине стека значений и исполняется следующая инструкция,
LIST_APPEND
.
Многие операции байтового кода ссылаются на базовые типы, такие как PyUnicode
или
PyNumber
. Например, LIST_APPEND
добавляет в конец списка
некий объект. Для достижения этого он вытаскивает значение указателя из стека значений и возвращает значение указателя на самый последний
объект в своём стеке.
Такой макро является ярлыком для следующего:
PyObject *v = (*--stack_pointer);
Теперь указатель на obj
хранится как obj
. Полный список
указателей загружается из PEEK(oparg)
.
Затем для list
и v
вызываются списки API C для Python.
Соответствующий код для этого находится внутри Objects/listobject.c
,
который вы изучите в Главе 11, Объекты и типы.
Затем делается вызов 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
,
обладают некими аргументами операции, которые ссылаются на другие скомпилированные функции. В таком случае в этом потоке в имеющийся стек кадров
вставляется другой кадр и цикл вычисления выполняется для этой функции, пока эта функция не завершится.
При каждом создании нового кадра и его вставке в стек, соответствующее значение 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
function1()
File "example_stack.py", line 5, in function1
function2()
File "example_stack.py", line 2, in function2
raise RuntimeError
RuntimeError
В Lib/traceback.py
для отслеживания вы можете воспользоваться
walk_stack()
:
def walk_stack(f):
"""Walk a stack yielding the frame and line number for each frame.
This will follow f.f_back from the given frame. If no frame is given,
the current stack is used. Usually used with StackSummary.extract.
"""
if f is None:
f = sys._getframe().f_back.f_back
while f is not None:
yield f, f.f_lineno
f = f.f_back
Значение родителя предка (sys._getframe().f_back.f_back
) устанавливается в качестве кадра,
поскольку вы не желаете в своём отслеживании видеть вызов walk_stack()
или
print_trace()
. Указатель f_back
перемещается в самый верх
вызовов стека.
sys._getframe()
является API Python для получения значения атрибута
frame
текущего потока.
Вот как мог бы выглядит стек кадров с тремя кадрами, причём каждый со своим объектом кода, а состояние потока указывает на текущий кадр:
В этой главе вы получили введение в мозг CPython. Обсуждавшееся здесь ядро цикла вычислений является основным взаимодействием между скомпилированным кодом Python и лежащими в основе модулями расширения, библиотеками и системными вызовами C.
Некоторые темя в этой главе оказались смазанными, поскольку вы вернётесь к ним в последующих главах. Например, интерпретатор CPython обладает ядром цикла вычислений, однако вы можете обладать множеством запущенных в одно и то же самое время циклов, будь то параллельная или одновременная обработка.
CPython может обладать большим числом циклов вычислений, исполняющих множество кадров в системе. В последующей главе Главе 10, Параллельность и одновременность как используется система стека кадров для того, чтобы CPython работал на множестве ядер или ЦПУ. Кроме того, API объекта кадра CPython позволяет приостанавливать кадры и возобновлять их в виде асинхронного программирования.
Загрузка переменных при помощи стек значений требует выделения памяти и управления ею. Для действенной работы CPython ему требуется основательный процесс управления памятью. В нашей следующей главе вы изучите как работает управление памятью и как она соотносится с теми указателями PyObject, которые используются циклом вычислений.