Бинарный модуль расширения Python с C-интерфейсом

Зачем нужно

Документация на сайте python
https://docs.python.org/3/extending/extending.html

Выжимка из документации, для старта

  1. В начале заголовочного файла импортировать объявить макрос PY_SSIZE_T_CLEAN и <Python.h>
  2. Создать структуру с описанием модуля
  3. Нужно объявить функцию функцию инициализации модуля 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");
}

Работа с аргументами

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

Для разбора входящих аргументов нужно:

  1. Правильно задать PyMethodDef.ml_flags, например METH_NOARGS или METH_VARARGS
  2. Входящие аргументы передаются через один объект PyObject *args. Для его разбора на отдельные аргументы вызываются доп. функции парсинга аргументов.
  3. Для разбора позиционных аргументов задаётся формат. В нём описывается количество и типы ожидаемых аргументов

Как "самый простой" способ без строки формата упоминается функция 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

Py_BuildValue

Ссылки