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