Встраивание Python в другое приложение
В предыдущих главах обсуждалось расширение Python, то есть добавление в него функциональности с помощью библиотеки функций на C. Возможно и обратное: расширить приложение на C/C++, встроив в него Python. Встраивание позволяет реализовать часть функциональности приложения на Python, а не на C или C++. Это можно использовать для разных целей; например, пользователи смогут настраивать приложение под свои нужды, написав несколько скриптов на Python. Вы также можете использовать 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[])
{
wchar_t *program = Py_DecodeLocale(argv[0], NULL);
if (program == NULL) {
fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
exit(1);
}
Py_SetProgramName(program); /* optional but recommended */
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is', ctime(time()))\n");
if (Py_FinalizeEx() < 0) {
exit(120);
}
PyMem_RawFree(program);
return 0;
}
Функцию Py_SetProgramName() следует вызвать до Py_Initialize(), чтобы сообщить интерпретатору пути к библиотекам времени выполнения 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_FromString().
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 EmbMethods[] = {
{"numargs", emb_numargs, METH_VARARGS,
"Return the number of arguments received by the process."},
{NULL, NULL, 0, NULL}
};
static PyModuleDef EmbModule = {
PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
NULL, NULL, NULL, NULL
};
static PyObject*
PyInit_emb(void)
{
return PyModule_Create(&EmbModule);
}
Вставьте приведённый выше код непосредственно перед функцией 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 и самостоятельно скомпилированным 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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/extending/embedding.html