Встраивание Python в другое приложение
В предыдущих главах обсуждалось, как расширять 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.
См. также
- Справочник по Python/C API
-
Подробности C-интерфейса Python приведены в данном руководстве. Здесь можно найти много необходимой информации.
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 — это практически один и тот же процесс, несмотря на разный замысел. Большинство тем, обсуждаемых в предыдущих главах, по-прежнему актуальны. Чтобы показать это, рассмотрим, что на самом деле делает код расширения 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())
В реальном приложении методы будут экспонировать API приложения в Python.
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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/extending/embedding.html