Вызов кода Python из 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.