Документация

Материал из pyhrol.ru
(перенаправлено с «Документация»)
Перейти к: навигация, поиск
Другие языки:English 99% • ‎русский 100%


Данная статья задумана как дополнение к примерам. Многое из сказанного ниже становится очевидным по ходу разбора примеров и откровенно дублируется из указателя.

Содержание

Вкратце

Чтобы питонизировать класс MyClass необходимо:

  1. Наследовать от TypeWrapper (в более специфических случаях от любого другого потомка TypeBase). Пусть наследник PyType, T = MyClass
  2. Для каждого метода MyClass определить соответствующий метод в PyType с одной из двух сигнатур и макросами согласно типам аргументов
  3. Переопределить методы TypeWrapper::constructor и TypeWrapper::destructor для того, чтобы объект MyClass мог быть создан и удален Python-ом
  4. Зарегистрировать вышеуказанные методы, предпочтительнее в конструкторе PyType
  5. Создать и зарегистрировать экземпляр класса PyType

см пример 0420

Чтобы питонизировать функцию, необходимо по аналогии с классом выполнить пп 2 и 4, см пример 0010

Подробнее

Сигнатуры

Все питонизированные функции имеют одинаковую сигнатуру:

void function(pyhrol::Tuples &);

а методы имеют одну из двух сигнатур (вторая для константных методов):

void method(const pyhrol::Ptr<MyClass> &, pyhrol::Tuples &) const;
void method(const pyhrol::Ptr<const MyClass> &, pyhrol::Tuples &) const;

кроме того, бывают статические методы:

static void method(pyhrol::Tuples &);

и методы класса:

static void method(const PyTypeObject &, pyhrol::Tuples &);

где function, method и MyClass названия функции, метода и класса соответственно.

Базовые классы

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

class PyType: public pyhrol::TypeWrapper<MyClass>
{
PyType()
: TypeBase<MyClass>("MyClass", "help")
{
}
 
virtual void destructor(MyClass &obj) const
{
obj.~MyClass();
}
 
static void init() __attribute__ ((constructor))
{
m_get(new PyType);
}
};

Сверху вниз:

  • Класс PyType — питонизатор класса MyClass. Должен существовать в единственном экземпляре и по сути является оболочкой над PyTypeObject
  • Все методы класса PyType в данном примере закрытые; это безопаснее с одной стороны и, с другой стороны, необходимости в их вызове извне нет.
  • Все потомки TypeBase наследуют виртуально, поэтому "базовый" конструктор — TypeBase. Первый аргумент — имя класса в python-е; должно удовлетворять правилам именования типов в этом языке и его выбор никак не связан с именами упомянутых классов, должен быть литералом или значением, хранящемся не на стеке. Второй — описание нового класса, выводится первой строкой по команде help.
  • Единственный обязательный для переопределения метод — destructor. Вызывается когда python разрушает последнюю переменную, ссылающуюся на объект данного класса. Если MyClass синглетон, необходимо оставить пустым.
  • Метод init создает экземпляр класса PyType и сохраняет его в статической переменной, определенной предком. Таким образом предок с данным шаблоном MyClass — синглетон. Он содержит в себе экземпляр PyTypeObject через который python обращается ко всем зарегистрированным методам и членам класса MyClass. Заставить быть синглетонами всех своих потомков TypeBase не может. Закрытый метод init, вызываемый при инициализации библиотеки — один из способов сделать синглетоном PyType. Если метод TypeBase::m_get выполнить дважды, вызывается исключение pyhrol::TypeInitException, которое, не будучи пойманным, приведет к [[1]] завершению процесса. Если метод не вызывать — класс PyType не будет инициализирован, python будет себя вести как если бы его вообще не было, однако любое обращение к нему из контекстов других классов и функций опять же приведет к исключению pyhrol::TypeInitException. Питонизированный класс PyType должен быть инициализирован единожды (!)

См список всех потомков TypeBase и их методов

Методы

Питонизированный метод должен содержать как минимум 3 макроса и обязательно в указанном порядке:

void PyType::method(const pyhrol::Ptr<const MyClass> &obj, pyhrol::Tuples &_args) const
{
//start of parameters definition area
 
//end of parameters definition area
 
PYHROL_AFTER_PARSE_TUPLE(_args)
PYHROL_AFTER_BUILD_VALUE(_args)
 
//start of execution area
obj->method();
//end of execution area
 
PYHROL_AFTER_EXECUTE_DEFAULT(_args)
}

Пример выше достаточен только для void-метода без аргументов а в общем случае выглядит так:

void PyType::method(const pyhrol::Ptr<const MyClass> &obj, pyhrol::Tuples &_args) const
{
//start of parameters definition area
 
//end of parameters definition area
 
PYHROL_PARSE_TUPLE_M("help", _args/*, arg1, ..., argM*/)
PYHROL_AFTER_PARSE_TUPLE(_args)
PYHROL_BUILD_VALUE_N("help", _args/*, res1, ..., resN*/)
PYHROL_AFTER_BUILD_VALUE(_args)
 
//start of execution area
obj->method(/*arg1, arg2, ..., res1, res2, ...*/);
//end of execution area
 
PYHROL_AFTER_EXECUTE_DEFAULT(_args)
}

где M и N целые неотрицательные числа не превышающие PYHROL_MAX_INPUT_ARGS и PYHROL_MAX_OUTPUT_ARGS соответственно. M определяет количество аргументов arg1, ..., argM, в то время как количество аргументов соответствующего макроса на 2 больше. N аналогично опеределяет количество возвращаемых значений res1, ..., resN.

Сверху вниз:

  • Все параметры должны быть объявлены в зоне объявления, а необязательные аргументы должны быть инициализированы там же.
  • Каждый макрос PYHROL_PARSE_TUPLE_M соответствует отдельной сигнатуре вызываемого метода. Отсутствие макросов означает, что метод не имеет аргументов. Макрос PYHROL_PARSE_TUPLE_0 успешно выполняется только при вызове без аргументов. Все макросы выполняются последовательно до тех пор, пока один из них не завершится успешно. В противном случае метод завершается ошибкой TypeError. Метод Tuples::parsed_variant возвращает номер подходящей сигнатуры, нумерация с 0. Группа аргументов arg1, ..., argM, соответствующая значению Tuples::parsed_variant гарантированно инициализирована, см пример.
  • Каждый макрос PYHROL_BUILD_VALUE_N соответствует отдельному типу возвращаемого значения. Отсутствие макросов означает, что метод не имеет возвращаемых значений, а его питонизированная версия при этом возвращает None. Макрос PYHROL_BUILD_VALUE_0 может быть использован для случая когда наряду с осязаемыми значениями метод должен возвращать None. В случае, когда макросов несколько, номер выбранного варианта передается через макрос PYHROL_AFTER_EXECUTE, нумерация с 0. см пример.
  • В зоне исполнения может находиться любой код, подразумевающий, собственно, использование объекта obj. Последний имеет операторы преобразования к типу MyClass и PyObject, см Ptr.
  • Макрос PYHROL_AFTER_EXECUTE_DEFAULT обязателен. В случае нескольких вариантов возвращаемых значений заменяется на PYHROL_AFTER_EXECUTE, который в качестве второго аргумента принимает номер выбранного варианта, нумерация с 0.

Нельзя вставлять вызов метода внутрь макросов, а также изменять порядок следования или пропускать обязательные макросы (!). Это ошибка и она выявляется только в режиме исполнения

Функции

Питонизированная функция является тривиальным упрощением метода и отличается от него только сигнатурой

Статические методы

Статические в терминах C++ методы делятся на статические методы и методы класса в терминах python. Они имеют разную сигнатуру. Для регистрации статических методов питонизирующий класс должен наследовать от TypeSpecial. см регистрация и пример

Аргументы

Аргументы функции или метода преобразуются из PyObject одним из макросов PYHROL_PARSE_TUPLE_{M}, где M — количество аргументов. Возможные типы аргументов перечислены в таблице типов. Есть возможность расширить список поддерживаемых типов с помощью конвертеров, см пример 0100

Возвращаемые значения

Значения, возвращаемые функцией или методом преобразуются в PyObject одним из макросов PYHROL_BUILD_VALUE_{N}, где N — количество переменных. Возможные типы переменных перечислены в таблице типов. Есть возможность расширить список поддерживаемых типов с помощью конвертеров, см пример 0100

Дополнительные параметры

Метод Tuples::set_options меняет поведение следующих за ним макросов PYHROL_PARSE_TUPLE_* или PYHROL_BUILD_VALUE_* и служит для:

  • указания количества обязательных аргументов
  • указания типа аргумента или возвращаемого значения
  • изменения форматной строки

см Tuples::set_options

Поля

Открытые поля класса могут быть питонизированы непосредственно, минуя всевозможные промежуточные функции. Их достаточно зарегистрировать. Многообразие типов питонизируемых членов ограничено таблицей и продемонстрировано в примере 0440

Регистрация

Понятно, что питонизированный метод или функция сам по себе не объявится в Python-е, его нужно зарегистрировать. Делается это макросами регистрации или методами m_add_method, m_add_getter, TypeSpecial::m_add_static_method и др. У каждого потомка TypeBase свой набор таких методов

Регистрация должна произойти до того, как Python выполнит функцию init<модуль>. Для этого используются либо глобальные статические переменные:

MyClass _G_myclass;

, либо функции/методы с атрибутом constructor:

static void init_myclass() __attribute__ ((constructor));
 
void init_myclass()
{
MyClass::init();
}

Правила перегрузки

В отличие от C++, где версия перегруженной функции выбирается на этапе компиляции, в pyhrol-е перегрузка осуществляется в режиме исполнения и никак не защищена от неожиданностей, возможно неприятных. Функция (или метод) может содержать любое количество макросов PYHROL_PARSE_TUPLE_*, каждый из которых представляет отдельную сигнатуру. Макросы выполняются последовательно пока один из них не выполнит преобразование безошибочно, тогда остальные игнорируются. Очевидно, что если аргумент может быть преобразован к одному из нескольких типов, он будет преобразован к тому, который проверяется первым. Например:

char c;
int i;
uint8_t n;
float f;
 
PYHROL_PARSE_TUPLE_1(NULL, _args, c)
PYHROL_PARSE_TUPLE_1(NULL, _args, i)
PYHROL_PARSE_TUPLE_1(NULL, _args, n)
PYHROL_PARSE_TUPLE_1(NULL, _args, f)
 
...

Возможные значения аргументов (макросы нумеруются с 0):

Значение Номер макроса
'a' 0
1 1
1.1 3
43333333333333333 3

Переменная n в макросе 2 не инициализируется никогда; целое число, которое не умещается в C-тип int затем успешно преобразовывается к float (макрос 3). Сложнее обстоит дело с наследующими друг от друга объектами — макросы должны следовать от потомка к предку. С другой стороны важен вопрос производительности, ибо каждая тщетная попытка преобразования требует процессорного времени, поэтому наиболее вероятные типы данных имеет смысл ставить выше.

см пример 0060

Создание объектов

Питонизируемый класс получает в наследство от TypeBase виртуальный метод TypeBase::constructor, имплементация которого вызывает ошибку NotImplementedError. К нему применимы все правила преобразования типов и перегрузки аргументов. Питонизирующий класс переопределяет этот метод согласно потребностям питонизируемого конструктора или не переопределяет его вообще, чтобы не дать возможность Python-у создать экземпляр класса. Для разрушения объекта служит метод TypeBase::destructor, который, по понятным причинам, имплементации не имеет

см примеры 0410, 0470, 0560

Счетчики ссылок

Соблюдение правил подсчета ссылок на объекты ложится на плечи пользователя. Указатель на PyObject, необходимый для Py_INCREF и Py_DECREF можно получить из:

Ptr является аргументом любого питонизирующего метода, TypeBase::argObject используется для гарантированного преобразования типа (см примеры 0505, 0550). У Ptr есть метод и оператор, возвращающий PyObject, в TypeBase::argObject последний является публичным членом.

Скромная попытка автоматизировать подсчет ссылок для простейшего случая воплощена в классе AutoHolder и применена в примере 0520

Многопоточность

В многопоточной среде библиотека не тестировалась.

Очевидно небезопасным для совместного доступа является синглетон pyhrol::Container в момент выполнения команды import, хотя очень трудно представить ситуацию, при которой один поток использует метаданные одного модуля, а другой тем временем импортирует новый модуль.

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

Гипотетически, проблемной в многопоточной среде может быть область кода между первым макросом PYHROL_PARSE_TUPLE_* и PYHROL_AFTER_BUILD_VALUE и только при первом вызове

Обработка ошибок

Для обработки ошибок питонизируемая функция (или метод) может:

  • вызвать исключение, наследующее от std::exception
  • установить ошибку вызовом PyErr_Format

Стандартные исключения трансформируются в python-ошибки согласно таблице.

Использование исключений предпочтительнее, т. к. python-ошибки могут быть нечаянно затерты последующими вызовами PyErr_Clear или переопределены другими вызовами PyErr_Format. Это может произойти по недосмотру ниже в той же функции (или в результате ошибки в самой библиотеке pyhrol). На момент инициирования ошибки вся ненужная не стековая память должна быть освобождена.

см пример 0110

Справка

Именованные аргументы

Список имен аргументов генерируется автоматически на основании l-value, передаваемых в макрос PYHROL_PARSE_TUPLE_*. Последний анализирует строковое представление всех l-value и генерирует массив параметров, который затем становится 4-м аргументом функции PyArg_VaParseTupleAndKeywords. Функции должны регистрироваться макросом PYHROL_REGISTER_FUNCTION_WITH_KEYWORDS, а методы -- PYHROL_REGISTER_METHOD_WITH_KEYWORDS.

В примере ниже:

int arg[3];
someStruct A, B, C;
int val;
 
PYHROL_PARSE_TUPLE_3(NULL, _args, arg[0], arg[1], arg[2])
PYHROL_PARSE_TUPLE_3(NULL, _args, A.val, B.val, C.val)
PYHROL_PARSE_TUPLE_2(NULL, _args, val, val)

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

function(...)
->    iii
      int32_t                       i   arg0
      int32_t                       i   arg1
      int32_t                       i   arg2
->    fff
      float                         f   A
      float                         f   B
      float                         f   C
->    *** Errors:
      Following arguments: 0, 1 has no unique name parts

, а вызывать функцию можно так:

function(1, arg2 = -1, arg1 = 7)
function(.3, C = -1.1, B = 2.1)

а вот третья версия работать не будет никак:

AttributeError: Tuple format invalid, it has format error: Following arguments: 0, 1 has no unique name parts (function)

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

см пример 0050

Пространства имён

Варианты
Просмотры
Действия
Навигация