Использование интерпретатора Python внутри программы на C

Раздел: Продвинутый Python -> Взаимодействие с C

Интеграция Python в приложения на C позволяет использовать преимущества скриптового языка для конфигурации, расширения функциональности или быстрого прототипирования. Основной инструмент - Python/C API, входящий в состав дистрибутива Python. Ниже рассмотрены основные подходы с примерами кода и указанием типичных проблем.

Основные методы встраивания Python в C

Базовый сценарий: инициализация интерпретатора, выполнение простого скрипта и завершение. Код на C:

#include <Python.h>

int main() {
    Py_Initialize();
    PyRun_SimpleString("print('Hello from Python')");
    Py_Finalize();
    return 0;
}

Python list in c (использование списков python в c)

Компиляция: gcc -o embed embed.c -I/usr/include/python3.10 -lpython3.10. Параметры зависят от версии Python и путей установки.

Типичная ошибка:

undefined reference to Py_Initialize - отсутствует флаг -lpython3.X. Решение: проверить версию и пути (например, pkg-config --cflags --libs python3).

Как выполнить произвольный код Python и получить результат вычислений?

Использовать PyRun_String для выполнения выражения и PyObject для извлечения результата. Пример:

PyObject *pModule, *pFunc, *pArgs, *pResult;
Py_Initialize();
pModule = PyImport_ImportModule("math");
pFunc = PyObject_GetAttrString(pModule, "sqrt");
pArgs = PyTuple_Pack(1, PyFloat_FromDouble(25.0));
pResult = PyObject_CallObject(pFunc, pArgs);
double res = PyFloat_AsDouble(pResult);
printf("sqrt(25) = %f\n", res);
Py_DECREF(pResult);
Py_DECREF(pArgs);
Py_DECREF(pFunc);
Py_DECREF(pModule);
Py_Finalize();

Use python in c (использование python в коде c (встраивание))

Проблема:

Утечка памяти при отсутствии Py_DECREF для каждого созданного объекта. Важно уменьшать счётчик ссылок после использования.

Как загрузить модуль Python и вызвать его функцию с передачей данных?

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

PyObject *pList = PyList_New(3);
for (int i = 0; i < 3; i++) {
    PyList_SetItem(pList, i, PyLong_FromLong(i*10));
}
pArgs = PyTuple_Pack(1, pList);
pResult = PyObject_CallObject(pFunc, pArgs);

Python c types (библиотека ctypes в python)

После вызова обязательно освободить ссылки: Py_DECREF(pList) (если не передаёте владение) и другие.

Ошибка:

SystemError: NULL result without error in PyObject_Call - функция не найдена или модуль не импортирован. Проверить имя функции и путь к модулю.

Как обрабатывать исключения Python в коде C?

После вызова Python-функции проверить наличие ошибки с помощью PyErr_Occurred() и вывести её:

if (PyErr_Occurred()) {
    PyErr_Print(); /* печатает в stderr */
    PyErr_Clear();
}

Python load c lib (загрузка c библиотеки в python)

Для детального анализа использовать PyErr_Fetch (получить тип, значение, traceback).

Проблема:

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

Как встраивать Python в многопоточное приложение?

Управление GIL с помощью пары PyGILState_Ensure / PyGILState_Release:

PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
/* работа с Python API */
PyGILState_Release(gstate);

Инициализацию интерпретатора выполнять однократно до создания потоков.

Ошибка:

Вызов API без захвата GIL приводит к падению или взаимоблокировке. Всегда использовать PyGILState_Ensure в каждом потоке, где работают с Python.

Расширенные примеры встраивания

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

Пример
/* Пример работы с numpy: создание массива из C и вызов Python-функции */
#include <numpy/arrayobject.h>

void init_numpy() {
    import_array(); /* макрос для инициализации numpy */
}

int main() {
    Py_Initialize();
    init_numpy();

    npy_intp dims[1] = {5};
    PyObject *pArray = PyArray_SimpleNew(1, dims, NPY_FLOAT64);
    double *data = (double*)PyArray_DATA(pArray);
    for (int i = 0; i < 5; i++) data[i] = i * 1.1;

    PyObject *pModule = PyImport_ImportModule("numpy");
    PyObject *pFunc = PyObject_GetAttrString(pModule, "mean");
    PyObject *pArgs = PyTuple_Pack(1, pArray);
    PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
    double mean = PyFloat_AsDouble(pResult);
    printf("Mean = %f\n", mean);

    Py_DECREF(pResult); Py_DECREF(pArgs);
    Py_DECREF(pFunc); Py_DECREF(pModule); Py_DECREF(pArray);
    Py_Finalize();
    return 0;
}
Mean = 2.200000
Пример
/* Встраивание в многопоточном контексте с использованием PyEval_EvalCode */
#include <pthread.h>

void* thread_func(void* arg) {
    PyGILState_STATE gstate = PyGILState_Ensure();
    PyObject *pGlobal = PyDict_New();
    PyObject *pCode = Py_CompileString("result = thread_id * 2", "<inline>", Py_file_input);
    PyObject *pLocal = PyDict_New();
    PyDict_SetItemString(pLocal, "thread_id", PyLong_FromLong((long)arg));
    PyEval_EvalCode(pCode, pGlobal, pLocal);
    PyObject *pResult = PyDict_GetItemString(pLocal, "result");
    long val = PyLong_AsLong(pResult);
    printf("Thread %ld: result = %ld\n", (long)arg, val);
    Py_DECREF(pLocal); Py_DECREF(pGlobal); Py_DECREF(pCode);
    PyGILState_Release(gstate);
    return NULL;
}

int main() {
    Py_Initialize();
    pthread_t threads[3];
    for (long i = 0; i < 3; i++)
        pthread_create(&threads[i], NULL, thread_func, (void*)i);
    for (int i = 0; i < 3; i++)
        pthread_join(threads[i], NULL);
    Py_Finalize();
    return 0;
}
Thread 0: result = 0
Thread 1: result = 2
Thread 2: result = 4

Примечание:

Для компиляции примеров требуется добавить флаги линковки numpy и pthreads (например, -lnumpy -lpthread).

Использование Python в коде C (встраивание) - comments

En
Use python in c (python)