Как использовать возможности Python внутри кода на 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 в потоке до инициализации интерпретатора.