Встраивание Python в другое приложение
В предыдущих главах обсуждалось расширение Python, то есть расширение функциональности Python путём подключения к нему библиотеки функций на C. Возможно и обратное: обогатить приложение на C/C++, встроив в него Python. Встраивание позволяет реализовать некоторые функции приложения на Python, а не на C или C++. Это можно использовать для многих целей; например, пользователи смогут настраивать приложение под свои нужды, написав несколько скриптов на Python. Вы также можете использовать Python сами, если некоторые функции проще реализовать на нём.
Встраивание Python похоже на его расширение, но не совсем то же самое. Разница заключается в том, что при расширении Python основной программой приложения остаётся интерпретатор Python, а при встраивании Python основная программа может не иметь к нему никакого отношения — вместо этого некоторые части приложения время от времени вызывают интерпретатор Python для выполнения кода на Python.
Итак, при встраивании Python вы предоставляете собственную основную программу. Одна из задач этой программы — инициализировать интерпретатор Python. Как минимум, необходимо вызвать функцию Py_Initialize(). Существуют необязательные вызовы для передачи Python аргументов командной строки. Позже можно вызвать интерпретатор из любой части приложения.
Есть несколько способов вызвать интерпретатор: можно передать строку с инструкциями Python в PyRun_SimpleString() или передать указатель на файл stdio и имя файла (используемое только для идентификации в сообщениях об ошибках) в PyRun_SimpleFile(). Можно также вызывать низкоуровневые операции, описанные в предыдущих главах, чтобы создавать и использовать объекты Python.
См. также
- Справочное руководство по API Python/C
-
Подробные сведения об интерфейсе Python для C приведены в этом руководстве. Здесь можно найти много необходимой информации.
1.1. Встраивание с использованием интерфейса очень высокого уровня
Самый простой способ встроить Python — использовать интерфейс очень высокого уровня. Этот интерфейс предназначен для выполнения скрипта Python без необходимости прямого взаимодействия с приложением. Например, его можно использовать для выполнения каких-либо операций над файлом.
#define PY_SSIZE_T_CLEAN
#include <Python.h>
int
main(int argc, char *argv[])
{
PyStatus status;
PyConfig config;
PyConfig_InitPythonConfig(&config);
/* optional but recommended */
status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
if (PyStatus_Exception(status)) {
goto exception;
}
status = Py_InitializeFromConfig(&config);
if (PyStatus_Exception(status)) {
goto exception;
}
PyConfig_Clear(&config);
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is', ctime(time()))\n");
if (Py_FinalizeEx() < 0) {
exit(120);
}
return 0;
exception:
PyConfig_Clear(&config);
Py_ExitStatusException(status);
}
Примечание
#define PY_SSIZE_T_CLEAN использовался для указания на то, что в некоторых API вместо int следует использовать Py_ssize_t. Начиная с Python 3.13 в нём нет необходимости, но он оставлен здесь для обратной совместимости. Описание этого макроса см. в разделе Строки и буферы.
Задать PyConfig.program_name следует до вызова Py_InitializeFromConfig(), чтобы сообщить интерпретатору пути к библиотекам времени выполнения Python. Затем интерпретатор Python инициализируется вызовом Py_Initialize(), после чего выполняется жёстко заданный скрипт Python, который выводит дату и время. Затем вызов Py_FinalizeEx() завершает работу интерпретатора, после чего программа заканчивается. В реальной программе скрипт Python может поступать из другого источника, например из процедуры текстового редактора, файла или базы данных. Получать код Python из файла удобнее с помощью функции PyRun_SimpleFile(): она избавляет от необходимости выделять память и загружать содержимое файла.
1.2. За пределами встраивания с использованием интерфейса очень высокого уровня: обзор
Интерфейс высокого уровня позволяет выполнять из приложения произвольные фрагменты кода Python, однако обмен значениями данных, мягко говоря, довольно неудобен. Для этого следует использовать вызовы более низкого уровня. За счёт большего объёма кода на C можно добиться практически любых результатов.
Следует отметить, что расширение Python и его встраивание — по сути, одно и то же занятие, несмотря на различие целей. Большинство тем, рассмотренных в предыдущих главах, остаются актуальными. Чтобы это показать, рассмотрим, что именно делает код расширения Python на C:
- Преобразует значения данных из Python в C,
- Вызывает функцию на C, используя преобразованные значения, и
- Преобразует значения данных, полученные в результате вызова, из C в Python.
При встраивании Python интерфейсный код выполняет следующие действия:
- Преобразует значения данных из C в Python,
- Вызывает интерфейсную функцию на Python, используя преобразованные значения, и
- Преобразует значения данных, полученные в результате вызова, из Python в C.
Как видно, этапы преобразования данных просто меняются местами, чтобы соответствовать обратному направлению передачи между языками. Отличается только функция, вызываемая между двумя этапами преобразования. При расширении вызывается функция на C, а при встраивании — функция на Python.
В этой главе не рассматривается преобразование данных из Python в C и наоборот. Также предполагается, что читатель понимает, как правильно работать со ссылками и обрабатывать ошибки. Поскольку эти аспекты не отличаются от расширения интерпретатора, необходимые сведения можно найти в предыдущих главах.
1.3. Чистое встраивание
Первая программа предназначена для вызова функции из скрипта Python. Как и в разделе об интерфейсе очень высокого уровня, интерпретатор Python не взаимодействует напрямую с приложением (но в следующем разделе это изменится).
Код для вызова функции, определённой в скрипте Python:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
int
main(int argc, char *argv[])
{
PyObject *pName, *pModule, *pFunc;
PyObject *pArgs, *pValue;
int i;
if (argc < 3) {
fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
return 1;
}
Py_Initialize();
pName = PyUnicode_DecodeFSDefault(argv[1]);
/* Error checking of pName left out */
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule != NULL) {
pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc is a new reference */
if (pFunc && PyCallable_Check(pFunc)) {
pArgs = PyTuple_New(argc - 3);
for (i = 0; i < argc - 3; ++i) {
pValue = PyLong_FromLong(atoi(argv[i + 3]));
if (!pValue) {
Py_DECREF(pArgs);
Py_DECREF(pModule);
fprintf(stderr, "Cannot convert argument\n");
return 1;
}
/* pValue reference stolen here: */
PyTuple_SetItem(pArgs, i, pValue);
}
pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
printf("Result of call: %ld\n", PyLong_AsLong(pValue));
Py_DECREF(pValue);
}
else {
Py_DECREF(pFunc);
Py_DECREF(pModule);
PyErr_Print();
fprintf(stderr,"Call failed\n");
return 1;
}
}
else {
if (PyErr_Occurred())
PyErr_Print();
fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);
}
else {
PyErr_Print();
fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
return 1;
}
if (Py_FinalizeEx() < 0) {
return 120;
}
return 0;
}
Этот код загружает скрипт Python с помощью argv[1] и вызывает функцию, имя которой указано в argv[2]. Её целочисленные аргументы — это остальные значения массива argv. Если скомпилировать и скомпоновать эту программу (пусть готовый исполняемый файл называется call) и использовать её для запуска скрипта Python, например такого:
def multiply(a,b):
print("Will compute", a, "times", b)
c = 0
for i in range(0, a):
c = c + b
return c
результатом будет:
$ call multiply multiply 3 2 Will compute 3 times 2 Result of call: 6
Хотя программа довольно велика для выполняемой ею задачи, большая часть кода посвящена преобразованию данных между Python и C и выводу сообщений об ошибках. Интересная с точки зрения встраивания Python часть начинается с
Py_Initialize(); pName = PyUnicode_DecodeFSDefault(argv[1]); /* Error checking of pName left out */ pModule = PyImport_Import(pName);
После инициализации интерпретатора скрипт загружается с помощью PyImport_Import(). Эта функция принимает строку Python, созданную с помощью функции преобразования данных PyUnicode_DecodeFSDefault().
pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc is a new reference */
if (pFunc && PyCallable_Check(pFunc)) {
...
}
Py_XDECREF(pFunc);
После загрузки скрипта нужное имя извлекается с помощью PyObject_GetAttrString(). Если имя существует и возвращённый объект является вызываемым, можно считать, что это функция. Затем программа, как обычно, создаёт кортеж аргументов. Вызов функции Python выполняется следующим образом:
pValue = PyObject_CallObject(pFunc, pArgs);
После возврата из функции pValue либо равно NULL, либо содержит ссылку на возвращённое функцией значение. Не забудьте освободить ссылку после проверки значения.
1.4. Расширение встроенного Python
До сих пор у встроенного интерпретатора Python не было доступа к функциям самого приложения. API Python позволяет предоставить такой доступ, расширив встроенный интерпретатор. Иными словами, во встроенный интерпретатор добавляются функции, предоставляемые приложением. Это может показаться сложным, но на самом деле всё не так уж плохо. На время забудьте, что приложение запускает интерпретатор Python. Вместо этого представьте приложение как набор подпрограмм и напишите связующий код, который предоставит Python доступ к этим подпрограммам, — так же, как при написании обычного расширения Python. Например:
static int numargs=0;
/* Return the number of arguments of the application command line */
static PyObject*
emb_numargs(PyObject *self, PyObject *args)
{
if(!PyArg_ParseTuple(args, ":numargs"))
return NULL;
return PyLong_FromLong(numargs);
}
static PyMethodDef emb_module_methods[] = {
{"numargs", emb_numargs, METH_VARARGS,
"Return the number of arguments received by the process."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef emb_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "emb",
.m_size = 0,
.m_methods = emb_module_methods,
};
static PyObject*
PyInit_emb(void)
{
return PyModuleDef_Init(&emb_module);
}
Вставьте приведённый выше код непосредственно перед функцией main(). Также вставьте следующие две инструкции перед вызовом Py_Initialize():
numargs = argc;
PyImport_AppendInittab("emb", &PyInit_emb);
Эти две строки инициализируют переменную numargs и делают функцию emb.numargs() доступной встроенному интерпретатору Python. Благодаря этим расширениям скрипт Python может выполнять, например, следующее:
import emb
print("Number of arguments", emb.numargs())
В реальном приложении методы предоставят Python доступ к API приложения.
1.5. Встраивание Python в C++
Встроить Python можно и в программу на C++; точный способ зависит от особенностей используемой системы C++. Как правило, основную программу нужно написать на C++, а для её компиляции и компоновки использовать компилятор C++. Перекомпилировать сам Python с помощью C++ не требуется.
1.6. Компиляция и компоновка в Unix-подобных системах
Не всегда просто подобрать подходящие флаги компилятора (и компоновщика) для встраивания интерпретатора Python в приложение. Это особенно важно потому, что Python должен загружать библиотечные модули, реализованные как динамические расширения на C (файлы .so), скомпонованные с ним.
Чтобы узнать необходимые флаги компилятора и компоновщика, можно выполнить скрипт pythonX.Y-config, который создаётся в процессе установки (также может быть доступен скрипт python3-config). У этого скрипта есть несколько параметров; непосредственно вам пригодятся следующие:
-
pythonX.Y-config --cflagsвыводит рекомендуемые флаги для компиляции:$ /opt/bin/python3.11-config --cflags -I/opt/include/python3.11 -I/opt/include/python3.11 -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall
-
pythonX.Y-config --ldflags --embedвыводит рекомендуемые флаги для компоновки:$ /opt/bin/python3.11-config --ldflags --embed -L/opt/lib/python3.11/config-3.11-x86_64-linux-gnu -L/opt/lib -lpython3.11 -lpthread -ldl -lutil -lm
Примечание
Чтобы не перепутать несколько установок Python (особенно системную версию Python и самостоятельно скомпилированную), рекомендуется указывать абсолютный путь к pythonX.Y-config, как в примере выше.
Если этот способ вам не подходит (его работа не гарантируется на всех Unix-подобных платформах, однако мы будем рады сообщениям об ошибках), изучите документацию своей системы о динамической компоновке и/или проверьте файл Makefile Python (используйте sysconfig.get_makefile_filename(), чтобы узнать его расположение), а также параметры компиляции. В этом случае модуль sysconfig поможет программно извлечь значения конфигурации, которые потребуется объединить. Например:
>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'
© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/extending/embedding.html