Бинарный модуль расширения Python с C-интерфейсом
Зачем нужно
- Выносить куски проекта, критичные к скорости работы в компилируемые языки.
- Делать биндинги к библиотекам, недоступным в Python напрямую
- Добавлять к существующим проектам тесты с удобной python-инфраструктурой
- Добавлять к существующим проектам Gui на python
Документация на сайте python
https://docs.python.org/3/extending/extending.html
Выжимка из документации, для старта
- В начале заголовочного файла импортировать объявить макрос
PY_SSIZE_T_CLEAN
и<Python.h>
- Создать структуру с описанием модуля
- Нужно объявить функцию функцию инициализации модуля
PyInit_<module_name>
, где<module_name>
- имя нового модуля, внутри которой вызывать PyModule_Create.
В итоге код получается примерно такой:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "c_bind_python.h"
// перечисление функций, которые будут экспортироваться как python-функции модуля
static struct PyMethodDef methods[] = {
// Тут указывается массив структур PyMethodDef
// https://docs.python.org/3/c-api/structures.html#c.PyMethodDef
// Функция без аргументов
{"function_without_args", (PyCFunction)function_without_args, METH_NOARGS},
// Функция только с позиционными аргументами
{"function_with_tuple_args", (PyCFunction)function_args, METH_VARARGS}
{NULL, NULL}
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"go_for_python", // module name, same as for PyInit_...
NULL,
-1,
methods
};
PyMODINIT_FUNC PyInit_go_for_python(void) {
return PyModule_Create(&module);
}
Python-функции
Объявление функций
Без python-аргументов
static PyObject* function_without_args(PyObject* self, Py_UNUSED) {
return PyUnicode_FromString("hello function_without_args");
}
С позиционными аргументами
static PyObject* function_with_tuple_args(PyObject* self, PyObject* args){
return PyUnicode_FromString("hello function_with_tuple_args");
}
Работа с аргументами
Для разбора входящих аргументов нужно:
- Правильно задать PyMethodDef.ml_flags, например
METH_NOARGS
илиMETH_VARARGS
- Входящие аргументы передаются через один объект
PyObject *args
. Для его разбора на отдельные аргументы вызываются доп. функции парсинга аргументов. - Для разбора позиционных аргументов задаётся формат. В нём описывается количество и типы ожидаемых аргументов
Как "самый простой" способ без строки формата упоминается функция PyArg_UnpackTuple, она может прочитать аргументы в переданные PyObject-аргументы.
Чтение одного позиционного аргумента как UTF-8 строки
int _py_read_one_string_arg(PyObject *args, char **content, size_t *bytes_len){
return PyArg_ParseTuple(args, "s#", content, bytes_len);
}
Создание Python-объектов в C-коде
Целые числа
C.PyLong_FromLong(333)
Просто python-объект
Py_BuildValue