Вызов кода Python из C: практические сценарии интеграции

Раздел: Расширения -> Интеграция с C

Основы вызова Python из C

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

Самое эффективное решение для встраивания Python в программу на C заключается в использовании официального Python/C API. Этот API предоставляется дистрибутивом Python и позволяет инициализировать интерпретатор, выполнять код и завершать его работу.

#include <Python.h>

int main() {
    Py_Initialize();
    PyRun_SimpleString("print('Привет из Python')");
    Py_Finalize();
    return 0;
}

Python c code (примеры вызова c из python)

Пояснение шагов:

  • Py_Initialize() запускает встроенный интерпретатор Python.
  • PyRun_SimpleString выполняет строку как Python-код.
  • Py_Finalize() освобождает ресурсы интерпретатора.

Типичные ошибки и их решение:

  • При компиляции не указан путь к заголовочным файлам Python (флаг -I/usr/include/python3.x) и библиотеке (-lpython3.x).
  • Пропущен вызов Py_Finalize() - это приводит к утечке памяти.
  • Если приложение использует несколько потоков, необходимо дополнительно вызвать PyEval_InitThreads() до инициализации.

Как передать аргументы в функцию Python из C?

Для вызова определённой функции с аргументами используется последовательность: импорт модуля, получение атрибута функции, создание кортежа аргументов и вызов.

#include <Python.h>

int main() {
    Py_Initialize();
    
    // Импорт модуля math
    PyObject *pModule = PyImport_ImportModule("math");
    if (!pModule) { PyErr_Print(); return 1; }
    
    // Получение функции sqrt
    PyObject *pFunc = PyObject_GetAttrString(pModule, "sqrt");
    if (!pFunc || !PyCallable_Check(pFunc)) { PyErr_Print(); return 1; }
    
    // Подготовка аргумента: число 16.0
    PyObject *pArgs = Py_BuildValue("(f)", 16.0);
    
    // Вызов функции
    PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
    if (!pResult) { PyErr_Print(); return 1; }
    
    // Извлечение значения
    double result = PyFloat_AsDouble(pResult);
    printf("sqrt(16) = %f\n", result);
    
    // Освобождение памяти
    Py_XDECREF(pResult);
    Py_XDECREF(pArgs);
    Py_XDECREF(pFunc);
    Py_XDECREF(pModule);
    
    Py_Finalize();
    return 0;
}

C вызов python (вызов python из c)

Пояснение: Py_BuildValue формирует кортеж аргументов в зависимости от строки формата (здесь "(f)" - один float). После вызова PyObject_CallObject результат может быть любого типа. Его преобразуют в C-тип с помощью PyFloat_AsDouble. Важно освобождать все PyObject* счётчиком Py_XDECREF.

Проблемы: Если функция не вызывается (не callable) или модуль не импортируется, PyErr_Print() выводит ошибку в stderr. Следует проверять каждый полученный объект на NULL. Некорректный формат Py_BuildValue может вызвать segfault.

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

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

#include <Python.h>

int main() {
    Py_Initialize();
    
    PyObject *pModule = PyImport_ImportModule("math");
    PyObject *pFunc = PyObject_GetAttrString(pModule, "invalid_func");
    if (!pFunc) {
        // Если атрибут не найден, получаем объект исключения
        PyObject *pType, *pValue, *pTraceback;
        PyErr_Fetch(&pType, &pValue, &pTraceback);
        PyErr_NormalizeException(&pType, &pValue, &pTraceback);
        
        // Вывод в stderr
        PyErr_Display(pType, pValue, pTraceback);
        
        // Освобождение
        Py_XDECREF(pType);
        Py_XDECREF(pValue);
        Py_XDECREF(pTraceback);
    }
    
    Py_XDECREF(pModule);
    Py_Finalize();
    return 0;
}

библиотека dll python (библиотека для работы с dll в python)

Пояснение: PyErr_Fetch извлекает информацию об исключении. PyErr_NormalizeException гарантирует, что класс исключения корректен. PyErr_Display выводит трассу подобно Python. После обработки объекты освобождаются.

Ошибки: Если вызвать PyErr_Print вместо PyErr_Display, программа сама выведет ошибку и очистит стек, но получить детали без краша бывает сложнее. Не следует дважды обрабатывать одно исключение.

Как выполнить целый файл на Python из C?

Для запуска скрипта из файла используется PyRun_FileEx или PyRun_SimpleFileEx.

#include <Python.h>

int main() {
    Py_Initialize();
    
    FILE *fp = fopen("script.py", "r");
    if (!fp) { perror("fopen"); return 1; }
    
    PyRun_SimpleFileEx(fp, "script.py", 1);  // 1 – закрыть файл после выполнения
    // или PyRun_FileEx(fp, "script.py", Py_file_input, NULL, NULL);
    
    Py_Finalize();
    return 0;
}

Пояснение: Второй аргумент для PyRun_SimpleFileEx - имя файла (используется в сообщениях об ошибках). Третий аргумент 1 означает, что C автоматически закроет файл. Альтернатива PyRun_FileEx даёт больше контроля (можно передать глобальные/локальные словари).

Проблемы: Если файл не найден или содержит синтаксические ошибки, программа выведет ошибку в stderr. В многопоточных приложениях следует использовать PyGILState_Ensure.

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

При работе с пользовательскими модулями необходимо добавить путь к каталогу с этим модулем в sys.path.

#include <Python.h>

int main() {
    Py_Initialize();
    
    // Добавление текущего каталога в sys.path
    PyRun_SimpleString("import sys; sys.path.insert(0, '.')");
    
    // Импорт модуля mymodule
    PyObject *pMod = PyImport_ImportModule("mymodule");
    if (!pMod) { PyErr_Print(); return 1; }
    
    // Вызов функции mymodule.greet("Мир")
    PyObject *pResult = PyObject_CallMethod(pMod, "greet", "(s)", "Мир");
    if (!pResult) { PyErr_Print(); return 1; }
    
    printf("Результат: %s\n", PyUnicode_AsUTF8(pResult));
    
    Py_XDECREF(pResult);
    Py_DECREF(pMod);
    Py_Finalize();
    return 0;
}

Пояснение: PyObject_CallMethod удобен, когда нужно вызвать метод или функцию с именем и аргументами. Формат строки аналогичен Py_BuildValue. Для строковых результатов используется PyUnicode_AsUTF8.

Ошибки: Без добавления пути модуль не будет найден. Если функция возвращает None, PyUnicode_AsUTF8 приведёт к крашу.

Как обмениваться сложными типами (списками и словарями) между C и Python?

Для передачи массивов или ассоциативных массивов API предоставляет функции создания списков и словарей.

#include <Python.h>

int main() {
    Py_Initialize();
    
    // Создание списка [10, 20, 30]
    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));
    
    // Создание словаря {"a": 1, "b": 2}
    PyObject *pDict = PyDict_New();
    PyDict_SetItemString(pDict, "a", PyLong_FromLong(1));
    PyDict_SetItemString(pDict, "b", PyLong_FromLong(2));
    
    // Передача в Python-функцию (например, sum_list)
    PyObject *pModule = PyImport_ImportModule("builtins");
    PyObject *pFunc = PyObject_GetAttrString(pModule, "sum");
    PyObject *pArgs = PyTuple_Pack(1, pList);
    PyObject *pResult = PyObject_CallObject(pFunc, pArgs);
    printf("sum = %ld\n", PyLong_AsLong(pResult));
    
    // Освобождение
    Py_DECREF(pDict);
    Py_DECREF(pList);
    Py_DECREF(pResult);
    Py_DECREF(pArgs);
    Py_DECREF(pFunc);
    Py_DECREF(pModule);
    
    Py_Finalize();
    return 0;
}

Пояснение: PyList_New выделяет список заданной длины, затем элементы заполняются PyList_SetItem (обратите внимание: эта функция «ворует» ссылку, поэтому не нужно дополнительно декрементировать переданные объекты). Для словаря PyDict_SetItemString принимает ключ как const char*. Все созданные объекты должны быть освобождены.

Проблемы: Неправильное управление счётчиками ссылок - частая причина утечек или крашей. После PyList_SetItem не следует вызывать Py_DECREF на уже установленный элемент. При передаче списка в функцию, которая изменяет его, изменения будут видны в C после повторного чтения.

Как работать с массивами NumPy из C?

Если требуется высокая производительность, можно использовать NumPy C API. Для этого импортируется модуль numpy и создаётся массив через PyArray_SimpleNew.

#include <Python.h>
#include <numpy/arrayobject.h>

int main() {
    Py_Initialize();
    import_array();  // Инициализация NumPy API
    
    npy_intp dims[] = {3, 4};
    PyObject *pArray = PyArray_SimpleNew(2, dims, NPY_FLOAT64);
    
    // Заполнение данными
    double *data = (double*)PyArray_DATA(pArray);
    for(int i=0; i<12; i++) data[i] = i*1.5;
    
    // Передача в Python и получение среднего
    PyObject *pModule = PyImport_ImportModule("numpy");
    PyObject *pFunc = PyObject_GetAttrString(pModule, "mean");
    PyObject *pArgs = PyTuple_Pack(1, pArray);
    PyObject *pRes = PyObject_CallObject(pFunc, pArgs);
    double mean = PyFloat_AsDouble(pRes);
    printf("Среднее = %f\n", mean);
    
    Py_DECREF(pRes);
    Py_DECREF(pArgs);
    Py_DECREF(pFunc);
    Py_DECREF(pModule);
    Py_DECREF(pArray);
    Py_Finalize();
    return 0;
}

Пояснение: import_array() обязателен для использования NumPy C API. PyArray_SimpleNew создаёт непрерывный массив заданного типа. Доступ к данным осуществляется через макрос PyArray_DATA. Важно подключать заголовочный файл numpy/arrayobject.h.

Ошибки: Без вызова import_array() возникает segfault. Тип данных должен быть согласован: NPY_FLOAT64 соответствует double в C. При компоновке требуются флаги -lpython3.x -lnumpy.

Расширенные примеры вызова Python из C

Пример 1: Вызов функции Python с возвратом строки и её использование в C

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

int main() {
    Py_Initialize();

    // Импорт модуля builtins (для доступа к str.upper)
    PyObject *pBuiltins = PyImport_ImportModule("builtins");
    if (!pBuiltins) { PyErr_Print(); return 1; }

    // Получение метода upper строки
    PyObject *pStr = PyUnicode_FromString("hello world");
    PyObject *pUpper = PyObject_GetAttrString(pStr, "upper");
    if (!pUpper || !PyCallable_Check(pUpper)) { PyErr_Print(); return 1; }

    // Вызов метода (без аргументов)
    PyObject *pResult = PyObject_CallObject(pUpper, NULL);
    if (!pResult) { PyErr_Print(); return 1; }

    // Преобразование в C-строку
    const char *cstr = PyUnicode_AsUTF8(pResult);
    if (!cstr) { PyErr_Print(); return 1; }

    printf("Результат upper: %s\n", cstr);

    // Освобождение объектов
    Py_DECREF(pResult);
    Py_DECREF(pUpper);
    Py_DECREF(pStr);
    Py_DECREF(pBuiltins);

    Py_Finalize();
    return 0;
}
Результат upper: HELLO WORLD

Пояснение: В примере показан вызов метода объекта (строки) без передачи аргументов. PyUnicode_AsUTF8 возвращает указатель на внутреннюю память Python, поэтому строка действительна только пока на объект есть ссылка. После Py_DECREF эта память может быть освобождена.

Пример 2: Обработка исключения при вызове несуществующей функции

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

int main() {
    Py_Initialize();

    PyObject *pModule = PyImport_ImportModule("math");
    if (!pModule) { PyErr_Print(); return 1; }

    PyObject *pFunc = PyObject_GetAttrString(pModule, "nonexistent_func");
    if (!pFunc) {
        // Извлекаем информацию об исключении
        PyObject *pType, *pValue, *pTraceback;
        PyErr_Fetch(&pType, &pValue, &pTraceback);

        // Нормализация (на всякий случай)
        PyErr_NormalizeException(&pType, &pValue, &pTraceback);

        // Выводим подробную информацию
        PyObject *pStrType = PyObject_Str(pType);
        PyObject *pStrValue = PyObject_Str(pValue);
        printf("Ошибка: %s - %s\n", PyUnicode_AsUTF8(pStrType), PyUnicode_AsUTF8(pStrValue));

        // Освобождение
        Py_DECREF(pStrType);
        Py_DECREF(pStrValue);
        Py_XDECREF(pType);
        Py_XDECREF(pValue);
        Py_XDECREF(pTraceback);
    }

    Py_DECREF(pModule);
    Py_Finalize();
    return 0;
}
Ошибка: <class 'AttributeError'> - module 'math' has no attribute 'nonexistent_func'

Пояснение: Вместо вызова PyErr_Print, который вывод в stderr, здесь производится программный разбор исключения с помощью PyErr_Fetch и PyObject_Str. Это позволяет сформировать собственное сообщение и не прерывать работу приложения.

Пример 3: Передача списка с плавающими числами и вычисление суммы с помощью Python

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

int main() {
    Py_Initialize();

    // Создаём список из 5 doubles
    PyObject *pList = PyList_New(5);
    double values[] = {1.5, 2.7, 3.2, 4.9, 5.1};
    for (int i = 0; i < 5; i++) {
        PyList_SetItem(pList, i, PyFloat_FromDouble(values[i]));
    }

    // Используем встроенную функцию sum
    PyObject *pBuiltins = PyImport_ImportModule("builtins");
    PyObject *pSumFunc = PyObject_GetAttrString(pBuiltins, "sum");
    PyObject *pArgs = PyTuple_Pack(1, pList);
    PyObject *pResult = PyObject_CallObject(pSumFunc, pArgs);

    if (pResult) {
        double total = PyFloat_AsDouble(pResult);
        printf("Сумма: %.2f\n", total);
    } else {
        PyErr_Print();
    }

    // Освобождение
    Py_XDECREF(pResult);
    Py_DECREF(pArgs);
    Py_DECREF(pSumFunc);
    Py_DECREF(pBuiltins);
    Py_DECREF(pList);

    Py_Finalize();
    return 0;
}
Сумма: 17.40

Пояснение: Список заполняется поэлементно с помощью PyList_SetItem. Функция sum принимает любой итерируемый объект. Результат преобразуется обратно в double.

Вызов Python из C - comments

En
C вызов python (python)