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

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

Мы увидели, как Python-код превращается в дерево абстрактного синтаксиса и компилируется в объекты кода. Эти объекты кода содержат списки отдельных операций в форме байт-кода.

Но чтобы объекты кода заработали, им кое-чего не хватает. Им необходимы входные данные. В Python входные данные принимают вид локальных и глобальных переменных.

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

Код в CPython выполняется в центральном цикле, который называется циклом вычисления. Интерпретатор CPython вычисляет и выполняет объект кода, полученный из файла .pyc или от компилятора:

 

AST

 

CFG

 

-

 

 

 

 

 

 

 

 

 

 

 

В цикле вычисления берется каждая инструкция байт-кода и выполняется в кадровой системе стека.

ПРИМЕЧАНИЕ

Кадры стека — тип данных, используемый во многих средах выполне­ ния, не только в Python. Стековые кадры позволяют вызывать функции и возвращать переменные из вызовов. Также они содержат аргументы, локальные переменные и другую информацию с состоянием.

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

Важные термины    141

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

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

ИСХОДНЫЕ ФАЙЛЫ

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

ФАЙЛ

НАЗНАЧЕНИЕ

Python ceval.c

Базовая реализация цикла вычисления

Python ceval-gil.h

Определение GIL1 и управляющий алгоритм

ВАЖНЫЕ ТЕРМИНЫ

Ниже перечислены некоторые ключевые термины, встречающиеся в этой главе:

zz Цикл вычисления берет объект кода и преобразует его в серию объектов кадров.

zz Интерпретатор имеет хотя бы один поток (thread). zz Каждый поток характеризуется состоянием потока. zz Объекты кадров выполняются в стеке кадров.

zz Для ссылок на переменные используется стек значений.

1 Глобальная блокировка интерпретатора. — Примеч. ред.

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

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

ПОСТРОЕНИЕ СОСТОЯНИЯ ПОТОКА

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

CPython всегда имеет хотя бы один поток, и каждый поток обладает собственным состоянием.

СМ. ТАКЖЕ

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

Тип состояния потока

Тип состояния потока PyThreadState содержит свыше тридцати свойств, среди которых:

zz Уникальный идентификатор.

zz Связанный список с состояниями других потоков.

zz Состояние интерпретатора, которым был порожден поток. zz Кадр, выполняемый в настоящее время.

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

zz Необязательные функции трассировки.

zz Исключение, обрабатываемое в настоящий момент.

zz Асинхронное исключение, обрабатываемое в настоящий момент.

zz Стек поднятых исключений, если их несколько (например, внутри блока except).

zz Счетчик GIL.

zz Счетчики асинхронного генератора.

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

Построение объектов кадров    143

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

Исходники, относящиеся к состоянию потоков, распределены на несколько файлов:

ФАЙЛ

НАЗНАЧЕНИЕ

Python thread.c

Реализация API потоков

Include threadstate.h

Некоторые API состояния потока и определения типов

Include pystate.h

API состояния интерпретатора и определения типов

Include pythread.h

API потоков

Include cpython

Некоторые API потоков и состояния интерпретатора

pystate.h

 

ПОСТРОЕНИЕ ОБЪЕКТОВ КАДРОВ

Скомпилированные объекты кода вставляются в объекты кадров. Объекты кадров являются типом Python, поэтому к ним можно обращаться как из C, так и из Python.

Объекты кадров также содержат другие данные среды выполнения, необходимые для исполнения инструкций в объектах кода. Эти данные включают локальные переменные, глобальные переменные и встроенные модули.

Тип объекта кадра

Тип объекта кадра — PyObject со следующими дополнительными свойствами.

ПОЛЕ

ТИП

НАЗНАЧЕНИЕ

f_back

PyFrameObject *

Указатель на предыдущий кадр в стеке или

 

 

NULL для первого кадра

f_blockstack

PyTryBlock[]

Последовательность блоков for, try и loop

f_builtins

PyObject * (dict)

Таблица символических имен для модуля

 

 

builtin

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

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

ПОЛЕ

ТИП

НАЗНАЧЕНИЕ

f_code

PyCodeObject *

Объект кода, который нужно выполнить

f_executing

char

Флаг, указывающий, что кадр еще выполня-

 

 

ется

f_gen

PyObject *

Ссылка на генератор или NULL

f_globals

PyObject * (dict)

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

 

 

(PyDictObject)

f_iblock

int

Индекс кадра в f_blockstack

f_lasti

int

Последняя инструкция

f_lineno

int

Номер текущей строки

f_locals

PyObject *

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

 

 

(произвольное отображение)

f_localsplus

PyObject *[]

Объединение locals и stack

f_stacktop

PyObject **

Следующий свободный слот в f_valuestack

f_trace

PyObject *

Указатель на пользовательскую функцию

 

 

трассировки (см.«Трассировка выполнения

 

 

кадров»)

f_trace_lines

char

Переключение пользовательской функции

 

 

трассировки на трассировку на уровне

 

 

строки

f_trace_opcodes

char

Переключение пользовательской функции

 

 

трассировки на трассировку на уровне кода

 

 

операции

f_valuestack

PyObject **

Указатель на последнее локальное значение

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

Исходные файлы, относящиеся к объектам кадров:

ФАЙЛ НАЗНАЧЕНИЕ

Objects frameobject.c Реализация объекта кадра и Python API

Include frameobject.h

API объекта кадра и определение типа

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

Построение объектов кадров    145

API инициализации объекта кадра

API инициализации объекта кадра, PyEval_EvalCode(), является точкой входа для вычисления объекта кода и оберткой для внутренней функции

_PyEval\_EvalCode().

ПРИМЕЧАНИЕ

_PyEval_EvalCode() — сложная функция,которая определяет многие осо­ бенности поведения как объектов кадров, так и цикла интерпретатора. Очень важно понимать эту функцию,так как она демонстрирует некото­ рые принципы архитектуры интерпретатора CPython.

В этом разделе мы последовательно разберем логику _PyEval\_EvalCode().

_PyEval_EvalCode() определяет группу аргументов:

zz tstate: аргумент PyThreadState*, указывающий на состояние потока, в котором будет вычисляться этот код;

zz _co: аргумент PyCodeObject* с кодом, который должен быть помещен в объект кадра;

zz globals: PyObject* (dict) с именами переменных (ключи) и их значениями;

zz locals: PyObject* (dict) с именами переменных (ключи) и их значениями.

ПРИМЕЧАНИЕ

В Python локальные и глобальные переменные хранятся в виде словаря.

Для обращения к словарю можно воспользоваться встроенными функ­ циями locals() и globals():

>>>a = 1

>>>print(locals()["a"])

1

Другие аргументы не являются обязательными и не используются в базовом API:

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

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

zz argcount: количество позиционных аргументов;

zz args: PyObject* (tuple) со значениями позиционных аргументов;

zz closure: кортеж со строками, которые объединяются в поле co_freevars объекта кода;

zz defcount: длина списка значений по умолчанию для позиционных аргументов;

zz defs: список значений по умолчанию для позиционных аргументов; zz kwargs: список значений именованных аргументов;

zz kwcount: количество именованных аргументов;

zz kwdefs: словарь со значениями по умолчанию для именованных аргументов;

zz kwnames: список имен именованных аргументов; zz name: имя команды вычисления (строка);

zz qualname: уточненное имя команды вычисления (строка).

Вызов _PyFrame_New_NoTrack() создает новый кадр. Этот API также доступен из C API с использованием PyFrame_New(). _PyFrame_New_NoTrack() создает новый объект PyFrameObject по следующей схеме:

1.Свойству f_back присваивается последний кадр состояния потока.

2.С помощью установки значения свойства f_builtins и загрузки модуля builtins вызовом PyModule_GetDict() загружаются текущие встроенные функции.

3.Свойству f_code задается вычисляемый объект кода.

4.Свойству f_valuestack задается пустой стек значений.

5.Указателю f_stacktop присваивается f_valuestack.

6.Свойству f_globals присваивается аргумент globals.

7.Свойству f_locals присваивается новый словарь.

8.Свойству co_firstlineno присваивается значение f_lineno, чтобы трассировка содержала номера строк.

9.Всем остальным свойствам значение задается по умолчанию.

Теперь, когда создан новый экземпляр PyFrameObject, можно построить аргументы объекта кадра:

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

Построение объектов кадров    147

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

• •

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Преобразование именованных параметров в словарь

Определения функций могут содержать универсальное обозначение **kwargs для именованных аргументов:

def example(arg, arg2=None, **kwargs):

print(kwargs["x"], kwargs["y"]) # Преобразуется в ключ словаря

example(1, x=2, y=3)

# 2 3

В этом скрипте создается новый словарь, а непреобразованные аргументы копируются. Затем в локальной области видимости кадра создается переменная с именем kwargs.

Преобразование позиционных аргументов в переменные

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

def example(arg1, arg2): print(arg1, arg2)

example(1, 2) # 1 2

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

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

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

Упаковка позиционных аргументов в *args

Как и в случае с **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})

Преобразование значений из словаря именованных аргументов происходит после распаковки всех остальных аргументов. Для исключительно позиционных аргументов PEP 570 цикл именованных аргументов начинается с co_posonlyargcount. Если символ / использовался в третьем аргументе, то значение co_posonlyargcount будет равно 2.

PyDict_SetItem() вызывается для каждого оставшегося аргумента, чтобы он был добавлен в словарь locals. При выполнении каждый из именованных аргументов становится локальной переменной с соответствующей областью видимости.

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

Построение объектов кадров    149

ПРИМЕЧАНИЕ

Исключительно позиционные аргументы (position-only arguments) появи­ лись в Python 3.8.Представленные в PEP 570,исключительно позицион­ ные аргументы не позволяют пользователям вашего API использовать позиционные аргументы по синтаксису именованных.

Например, следующая простая функция преобразует температуру по Фаренгейту в температуру по шкале Цельсия. Обратите внимание на косую черту (/)— это специальный аргумент,отделяющий исключительно позиционные аргументы от других аргументов:

def to_celsius(fahrenheit, /, options=None): return (fahrenheit-32)*5/9

Всеаргументыслеваот/должныпередаватьсятолькокакпозиционные.Ар­ гументысправамогутпередаватьсякакпозиционныеиликакименованные:

>>> to_celsius(110)

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

TypeError:

>>> to_celsius(fahrenheit=110) Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: to_celsius() got some positional-only arguments passed as keyword arguments: 'fahrenheit'

Если именованный аргумент определяется со значением, то оно будет доступно в этой области видимости:

def example(arg1, arg2, example_kwarg=None):

print(example_kwarg) # example_kwarg уже является локальной переменной.

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

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

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