Как использовать возможности Python внутри кода на C: практическое руководство

Раздел: Прочее -> Взаимодействие с C/C++

Основные подходы к встраиванию Python в C

Встраивание интерпретатора Python в приложение на C позволяет использовать динамические возможности языка, обширные библиотеки (например, для обработки данных, веб-запросов) и ускорить разработку. Основной и наиболее эффективный способ - использование Python C API, предоставляемого стандартной библиотекой.

Python C API: полный контроль и производительность

Этот метод даёт прямой доступ ко всем функциям интерпретатора: инициализация, выполнение кода, передача объектов, управление памятью. Он требует детального управления ссылками (инкремент/декремент) и явной работы с GIL (Global Interpreter Lock) в многопоточных программах.

#include <Python.h>

int main() {
    // Инициализация интерпретатора
    Py_Initialize();
    if (!Py_IsInitialized()) {
        fprintf(stderr, "Ошибка инициализации Python\n");
        return 1;
    }

    // Выполнение простого выражения
    PyRun_SimpleString("print('Hello from Python!')");

    // Завершение
    Py_Finalize();
    return 0;
}

использование python в c (использование python в c)

Сборка: gcc -o demo demo.c -I/usr/include/python3.10 -lpython3.10 (путь к include и библиотека зависят от версии).

Типичные проблемы:

  • Пропуск Py_Initialize() - программа завершится с сегментацией.
  • Утечки памяти при неправильном управлении ссылками - используйте макросы Py_XDECREF и Py_DECREF.
  • Попытка вызвать Python из параллельных потоков без захвата GIL - используйте PyGILState_Ensure() и PyGILState_Release().
  • Забыли слинковать библиотеку -lpython3.x - ошибка компоновки.

Цели использования: когда требуется максимальная производительность, точный контроль над памятью и/или интеграция с существующим C-кодом.

Как выполнить произвольный Python-код из C?

Функции PyRun_SimpleString и PyRun_SimpleFile позволяют запускать строки или файлы. Они автоматически обрабатывают импорт модулей __main__.

Py_Initialize();
PyRun_SimpleString("import sys\nprint(sys.version)");
Py_Finalize();

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

Используйте PyImport_ImportModule, PyObject_GetAttrString и PyObject_CallObject.

PyObject *pModule = PyImport_ImportModule("mymod");
PyObject *pFunc = PyObject_GetAttrString(pModule, "my_func");
PyObject *pArgs = PyTuple_Pack(1, PyLong_FromLong(42));
PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
long result = PyLong_AsLong(pResult);
printf("Result: %ld\n", result);
Py_DECREF(pResult); Py_DECREF(pArgs); Py_DECREF(pFunc); Py_DECREF(pModule);

Если функция не существует - pFunc будет NULL, проверяйте перед вызовом. Также следите за подсчётом ссылок на аргументы.

Как передать из C в Python сложные данные (списки, словари)?

Python C API предоставляет конструкторы: PyList_New, PyDict_New, PyUnicode_DecodeFSDefault для строк. Пример создания списка целых чисел:

PyObject *pList = PyList_New(3);
PyList_SetItem(pList, 0, PyLong_FromLong(10));
PyList_SetItem(pList, 1, PyLong_FromLong(20));
PyList_SetItem(pList, 2, PyLong_FromLong(30));

Важно: при передаче владения через PyList_SetItem ссылка не увеличивается, не делайте INCREF после New.

Как работать с GIL в многопоточном C-приложении, встраивающем Python?

Если C-программа создаёт потоки, каждый поток, использующий Python, должен явно управлять GIL:

PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
// код на Python
PyGILState_Release(gstate);

Основная ошибка - вызов Python кода без захвата GIL из неглавного потока. Это ведёт к взаимоблокировкам или аварийному завершению. Все созданные до инициализации интерпретатора потоки не смогут использовать Python.

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

Пример 1: Вызов Python-функции с передачей списка и получением числа

Пусть есть модуль mymath.py:

Пример
# mymath.py
def sum_list(lst):
    return sum(lst)

Код на C:

Пример
#include <Python.h>
#include <stdio.h>

int main() {
    Py_Initialize();

    // Импортируем модуль
    PyObject *pModule = PyImport_ImportModule("mymath");
    if (!pModule) {
        PyErr_Print();
        return 1;
    }

    // Получаем функцию
    PyObject *pFunc = PyObject_GetAttrString(pModule, "sum_list");
    if (!pFunc || !PyCallable_Check(pFunc)) {
        PyErr_Print();
        return 1;
    }

    // Создаём список из 5 чисел
    PyObject *pList = PyList_New(5);
    for (int i = 0; i < 5; i++) {
        PyList_SetItem(pList, i, PyLong_FromLong(i * 10));
    }

    // Вызов функции с одним аргументом (списком)
    PyObject *pArgs = PyTuple_Pack(1, pList);
    PyObject *pResult = PyObject_CallObject(pFunc, pArgs);

    if (pResult) {
        long result = PyLong_AsLong(pResult);
        printf("Сумма списка: %ld\n", result);
        Py_DECREF(pResult);
    } else {
        PyErr_Print();
    }

    // Очистка
    Py_DECREF(pArgs);
    Py_XDECREF(pList);  // список уже находится в pArgs, но его ссылка не уменьшена? На самом деле PyTuple_Pack увеличивает ссылку на pList, поэтому мы должны её уменьшить после использования?
    // Упрощённая очистка: Py_DECREF(pList); // т.к. pList имеет 1 ссылку от PyList_New, и 1 от PyTuple_Pack
    // Но проще использовать Py_XDECREF(pList) после, но здесь мы передали владение в Tuple
    // Лучше сделать так:
    Py_DECREF(pList); // уменьшаем первоначальную ссылку (созданную New)
    // pList теперь имеет только ссылку внутри Tuple; Tuple будет уничтожен, и ссылка на pList уменьшена автоматически
    Py_DECREF(pFunc);
    Py_DECREF(pModule);

    Py_Finalize();
    return 0;
}

Результат выполнения:

Сумма списка: 100

Важно: в приведённом коде после PyTuple_Pack ссылка на pList увеличивается, поэтому мы должны сначала уменьшить исходную ссылку (созданную PyList_New), чтобы при уничтожении кортежа список не остался висеть. Но в коде выше Py_DECREF(pList) выполняется после Py_DECREF(pArgs) - это порядок важен, иначе pList может быть освобождён раньше, чем кортеж. Лучше явно управлять: перед Py_DECREF(pArgs) оставить ссылки как есть, или использовать Py_XDECREF после. В более простом варианте можно не уменьшать pList, а после всего сделать Py_DECREF(pList) после уничтожения кортежа, но это ненадёжно. Рекомендуется использовать Py_BuildValue с форматом (O) для создания кортежа, который автоматически забирает ссылку.

Пример 2: Обработка исключений Python в C

При вызове Python-функции может возникнуть исключение. Его нужно обработать, иначе состояние интерпретатора останется ошибочным.

Пример
PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
if (!pResult) {
    // Печать ошибки
    PyErr_Print();
    // Сброс состояния
    PyErr_Clear();
} else {
    // работа с результатом
    Py_DECREF(pResult);
}

Полный пример с намеренным вызовом несуществующего атрибута:

Пример
#include <Python.h>

int main() {
    Py_Initialize();
    PyObject *pMain = PyImport_AddModule("__main__");
    PyRun_SimpleString("x = 10");
    // Попытаемся получить атрибут 'y' - его нет
    PyObject *pY = PyObject_GetAttrString(pMain, "y");
    if (!pY) {
        printf("Ошибка: атрибут 'y' не найден\n");
        PyErr_Print();
        PyErr_Clear();
    } else {
        printf("y = %ld\n", PyLong_AsLong(pY));
        Py_DECREF(pY);
    }
    Py_Finalize();
    return 0;
}

Результат:

Ошибка: атрибут 'y' не найден
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: 'module' object has no attribute 'y'

Игнорирование проверки pResult == NULL приводит к непредсказуемому поведению. После вызова PyErr_Print() обязательно PyErr_Clear(), иначе следующая операция с Python может провалиться.

Пример 3: Многопоточное приложение с GIL

Допустим, C-приложение создаёт два потока, каждый из которых выполняет Python-код.

Пример
#include <stdio.h>
#include <pthread.h>
#include <Python.h>

void *thread_func(void *arg) {
    // Захватываем GIL
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();

    PyRun_SimpleString((char*)arg);

    // Освобождаем GIL
    PyGILState_Release(gstate);
    return NULL;
}

int main() {
    // Инициализация должна быть до работы с потоками
    Py_Initialize();
    // Разблокируем GIL для главного потока (так как мы не будем использовать Python в главном потоке)
    PyEval_ReleaseThread(PyGILState_GetThisThreadState());

    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_func, "print('Thread 1')");
    pthread_create(&t2, NULL, thread_func, "print('Thread 2')");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    // Теперь можно завершить интерпретатор
    PyEval_AcquireThread(PyGILState_GetThisThreadState());
    Py_Finalize();
    return 0;
}

Сборка: gcc -o threads threads.c -I/usr/include/python3.10 -lpython3.10 -lpthread

Результат:

Thread 1
Thread 2

Распространённая ошибка: вызов Py_Finalize() без предварительного захвата GIL (после того, как мы его освободили). В примере мы явно захватываем его перед финализацией. Также нельзя использовать PyRun_SimpleString в потоке до инициализации интерпретатора.

Использование Python в C - comments

En
использование python в c (python)