Встраивание 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 не имел доступа к функциональности самого приложения. Python API позволяет это сделать, расширяя встроенный интерпретатор. То есть встроенный интерпретатор расширяется с помощью процедур, предоставляемых приложением. Хотя это звучит сложно, это не так плохо. Просто на время забудьте, что приложение запускает интерпретатор 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.4-config --cflags -I/opt/include/python3.4m -I/opt/include/python3.4m -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
-
pythonX.Y-config --ldflagsдаст вам рекомендуемые флаги при компоновке:$ /opt/bin/python3.4-config --ldflags -L/opt/lib/python3.4/config-3.4m -lpthread -ldl -lutil -lm -lpython3.4m -Xlinker -export-dynamic
Примечание
Чтобы избежать путаницы между несколькими установками 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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/extending/embedding.html