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

Использование Instaviz для вывода объекта кода    133

ИСПОЛЬЗОВАНИЕ INSTAVIZ ДЛЯ ВЫВОДА ОБЪЕКТА КОДА

Все стадии работы компилятора можно собрать воедино с помощью модуля instaviz:

import instaviz

def foo(): a = 2**4

b = 1 + 5

c = [1, 4, 6] for i in c:

print(i)

else:

print(a) return c

instaviz.show(foo)

Этот фрагмент построит большое и сложное дерево графа AST. Инструкции байт-кода можно просмотреть по порядку:

Объект кода с именами переменных, константами и двоичным значением co_code:

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

134    Компилятор

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

ПРИМЕР: РЕАЛИЗАЦИЯ ОПЕРАТОРА «ПОЧТИ РАВНО»

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

Сначала необходимо добавить внутреннее определение #define для оператора Py_AlE, чтобы на него можно было ссылаться внутри расширенных функций сравнения для PyObject.

Откройте файл Include object.h и найдите следующие команды #define:

/* Коды операций расширенного сравнения */

#define Py_LT 0 #define Py_LE 1 #define Py_EQ 2 #define Py_NE 3

#define Py_GT 4 #define Py_GE 5

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

Пример: реализация оператора «почти равно»    135

Добавим дополнительное значение PyAlE со значением 6:

/* Новый оператор сравнения "почти равно" */

#define Py_AlE 6

Прямо под этим выражением располагается макрос Py_RETURN_RICHCOMPARE. Обновите этот макрос и добавьте в него условие case для Py_AlE:

/*

*Макрос для реализации расширенного сравнения

*

*Необходим макрос, потому что может использоваться

*любой C-совместимый тип

*/

#define Py_RETURN_RICHCOMPARE(val1, val2, op) \ do { \

switch (op) { \

case Py_EQ: if ((val1) == (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_NE: if ((val1) != (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_LT: if ((val1) < (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_GT: if ((val1) > (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_LE: if ((val1) <= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \ case Py_GE: if ((val1) >= (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE; \

/* + */ case Py_AlE: if ((val1) == (val2)) Py_RETURN_TRUE; Py_RETURN_FALSE;\ default: \

Py_UNREACHABLE(); \

}\

}while (0)

Вфайле Objects object.c присутствует проверка того, что оператор лежит в диапазоне от 0 до 5. Так как вы добавили значение 6, проверку необходимо обновить:

Objects object.c, строка 709

PyObject *

PyObject_RichCompare(PyObject *v, PyObject *w, int op)

{

PyThreadState *tstate = _PyThreadState_GET();

assert(Py_LT <= op && op <= Py_GE);

Замените последнюю строку следующей:

assert(Py_LT <= op && op <= Py_AlE);

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

136    Компилятор

Затем необходимо обновить код операции COMPARE_OP для поддержки Py_AlE как значения для типа оператора.

Сначала отредактируйте файл Objects object.c и добавьте Py_AlE в список _Py_SwappedOp. Этот список используется для определения того, содержит ли пользовательский класс только один магический (dunder) метод оператора, но не содержит другого.

Например, если вы определили класс Coordinate, оператор равенства можно определить реализацией магического метода __eq__:

class Coordinate:

def __init__(self, x, y): self.x = x

self.y = y

def __eq__(self, other):

if isinstance(other, Coordinate):

return (self.x == other.x and self.y == other.y) return super(self, other).__eq__(other)

И хотя вы не реализовали __ne__ (не равно, not equal) для Coordinate, CPython предполагает, что может применяться оператор, обратный __eq__:

>>> Coordinate(1, 100) != Coordinate(2, 400) True

В файле Objects object.c найдите список _Py_SwappedOp и добавьте Py_AlE в его конец. Затем добавьте "~=" в конец списка opstrings:

int _Py_SwappedOp[] = {Py_GT, Py_GE, Py_EQ, Py_NE, Py_LT, Py_LE, Py_AlE};

static const char * const opstrings[]

= {"<", "<=", "==", "!=", ">", ">=", "~="};

Откройте файл Lib/opcode.py и отредактируйте кортеж операторов расширенного сравнения:

cmp_op = ('<', '<=', '==', '!=', '>', '>=')

Добавьте новый оператор в конец кортежа:

cmp_op = ('<', '<=', '==', '!=', '>', '>=', '~=')

Список opstrings используется для сообщений об ошибках, если операторы расширенного сравнения не реализованы для класса.

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

Пример: реализация оператора «почти равно»    137

Теперь можно обновить компилятор для обработки условия в свойстве PyCmp_AlE в узле BinOp. Откройте файл Python compile.c и найдите compiler_ addcompare():

Python compile.c, строка 2479

static int compiler_addcompare(struct compiler *c, cmpop_ty op)

{

int cmp; switch (op) { case Eq:

cmp = Py_EQ; break;

case NotEq:

cmp = Py_NE; break;

case Lt:

cmp = Py_LT; break;

case LtE:

cmp = Py_LE; break;

case Gt:

cmp = Py_GT; break;

case GtE:

cmp = Py_GE; break;

Добавьте еще один оператор case в команду switch, чтобы сопоставить comp_op AlE дерева абстрактного синтаксиса с кодом операции сравнения PyCmp_AlE:

...

case AlE:

cmp = Py_AlE; break;

Теперь можно запрограммировать поведение оператора «почти равно» по следующему сценарию:

zz 1 ~= 2 False;

zz 1 ~= 1.01 True с округлением вниз.

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

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

138    Компилятор

API CPython содержит много функций для работы с типами PyLong (int) и PyFloat (float). Эта тема будет рассмотрена в главе «Объекты и типы».

Найдите функцию float_richcompare() в Objects floatobject.c и добавьте в определение Compare: goto следующее условие case:

Objects floatobject.c, строка 358

static PyObject*

float_richcompare(PyObject *v, PyObject *w, int op)

{

...

case Py_GT:

r = i > j; break;

/* НАЧАЛО нового кода */ case Py_AlE: {

double diff = fabs(i - j);

double rel_tol = 1e-9; // Относительная погрешность double abs_tol = 0.1; // Абсолютная погрешность

r = (((diff <= fabs(rel_tol * j)) || (diff <= fabs(rel_tol * i))) || (diff <= abs_tol));

}

break;

}

/* КОНЕЦ нового кода */ return PyBool_FromLong(r);

Этот код обрабатывает сравнение чисел с плавающей точкой при использовании оператора «почти равно». В нем используется логика, сходная с логикой math.isclose(), определенной в PEP 485, но с жестко закодированной абсолютной погрешностью 0.1.

Также необходимо внести изменения еще в одну защитную проверку, которая находится в цикле вычисления Python ceval.c. Цикл вычисления рассматривается в следующей главе.

Найдите следующий фрагмент кода:

...

case TARGET(COMPARE_OP): { assert(oparg <= Py_GE);

Замените проверку следующей:

assert(oparg <= Py_AlE);

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