Содержание страницы
5. Встраивание Python в другое приложение¶Embedding Python in Another Application
В предыдущих главах обсуждалось, как расширять Python, то есть как расширить функциональность Python, подключая к нему библиотеку на C. Также можно сделать наоборот: обогатить своё приложение на C/C++ путём встраивания в него Python. Встраивание даёт вашему приложению возможность реализовать часть функциональности приложения на Python, а не на C или C++. Это можно использовать для многих целей; один из примеров – позволить пользователям настраивать приложение под свои нужды, написав несколько скриптов на Python. Вы также можете использовать это сами, если какую-то функциональность проще написать на Python более легко.
Разница в том, что при расширении Python главная программа приложения остаётся интерпретатором Python, тогда как при встраивании Python главная программа может не иметь ничего общего с Python – вместо этого некоторые части приложения время от времени вызывают интерпретатор Python для выполнения некоторого кода на Python.
Итак, если вы встраиваете Python, вы предоставляете собственную главную программу. Одна из задач этой программы – инициализировать интерпретатор Python. Как минимум, необходимо вызвать функцию Py_Initialize(). При желании можно передать интерпретатору аргументы командной строки. Затем из любой части приложения можно вызывать интерпретатор.
Существует несколько способов вызвать интерпретатор: можно передать строку с выражениями Python в PyRun_SimpleString(), или передать указатель на файл stdio и имя файла (только для идентификации в сообщениях об ошибках) в PyRun_SimpleFile(). Также можно вызывать низкоуровневые операции, описанные в предыдущих главах, для создания и использования объектов Python.
Простая демонстрация встраивания Python находится в каталоге Demo/embed/ дистрибутива исходных кодов.
См. также
- Справочное руководство по Python/C API
- Подробности C-интерфейса Python приведены в этом руководстве. Много необходимой информации можно найти здесь.
5.1. Встраивание очень высокого уровня¶Very High Level Embedding
Простейшая форма встраивания Python – использование интерфейса сверхвысокого уровня. Этот интерфейс предназначен для выполнения Python-скрипта без необходимости взаимодействовать с приложением напрямую. Его можно использовать, например, для выполнения некоторой операции над файлом.
#include <Python.h>
int
main(int argc, char *argv[])
{
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
"print 'Today is',ctime(time())\n");
Py_Finalize();
return 0;
}
Приведённый выше код сначала инициализирует интерпретатор Python с помощью Py_Initialize(), затем выполняет жёстко заданный скрипт Python, который выводит дату и время. После этого вызов Py_Finalize() завершает работу интерпретатора, после чего программа завершается. В реальной программе скрипт Python может быть получен из другого источника – например, из текстового редактора, файла или базы данных. Получение кода Python из файла лучше выполнять с помощью функции PyRun_SimpleFile(), что избавляет от необходимости выделять память и загружать содержимое файла.
5.2. За рамками встраивания очень высокого уровня: обзор¶Beyond Very High Level Embedding: An overview
Высокоуровневый интерфейс позволяет выполнять произвольные фрагменты Python-кода из вашего приложения, но обмен значениями данных – как минимум весьма затруднителен. Если это необходимо, следует использовать низкоуровневые вызовы. Ценой написания большего количества кода на C можно достичь почти всего.
Следует отметить, что расширение Python и встраивание Python – по сути одно и то же действие, несмотря на разные цели. Большинство тем, рассмотренных в предыдущих главах, остаются актуальными. Чтобы показать это, рассмотрим, что на самом деле делает код расширения от Python к C:
- Преобразование значений данных из Python в C,
- Вызов функции C с использованием преобразованных значений и
- Преобразование значений данных из вызова из C в Python.
При встраивании Python код интерфейса делает:
- Преобразование значений данных из C в Python,
- Вызов функции интерфейса Python с использованием преобразованных значений и
- Преобразование значений данных из вызова из Python в C.
Как видите, шаги преобразования данных просто меняются местами для соответствия разному направлению межъязыковой передачи. Единственное отличие – это функция, которую вызывают между двумя преобразованиями данных. При расширении вызывается C-функция, при встраивании – Python-функция.
В этой главе не будет обсуждаться преобразование данных из Python в C и обратно. Также предполагается, что правильное использование ссылок и обработка ошибок понятны. Поскольку эти аспекты не отличаются от расширения интерпретатора, необходимые сведения можно найти в предыдущих главах.
5.3. Чистое встраивание¶Pure Embedding
Первая программа предназначена для выполнения функции из скрипта Python. Как и в разделе об интерфейсе очень высокого уровня, интерпретатор Python не взаимодействует напрямую с приложением (но это изменится в следующем разделе).
Код для выполнения функции, определённой в скрипте Python:
#include <Python.h>
int
main(int argc, char *argv[])
{
PyObject *pName, *pModule, *pDict, *pFunc;
PyObject *pArgs, *pValue;
int i;
if (argc < 3) {
fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
return 1;
}
Py_Initialize();
pName = PyString_FromString(argv[1]);
/* Проверка ошибок pName опущена */
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule != NULL) {
pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc – новая ссылка */
if (pFunc && PyCallable_Check(pFunc)) {
pArgs = PyTuple_New(argc - 3);
for (i = 0; i < argc - 3; ++i) {
pValue = PyInt_FromLong(atoi(argv[i + 3]));
if (!pValue) {
Py_DECREF(pArgs);
Py_DECREF(pModule);
fprintf(stderr, "Cannot convert argument\n");
return 1;
}
/* Ссылка pValue перехвачена здесь: */
PyTuple_SetItem(pArgs, i, pValue);
}
pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
printf("Result of call: %ld\n", PyInt_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;
}
Py_Finalize();
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 = PyString_FromString(argv[1]);
/* Проверка ошибок pName опущена */
pModule = PyImport_Import(pName);
После инициализации интерпретатора скрипт загружается с помощью PyImport_Import(). Эта функция требует в качестве аргумента строку Python, которая создаётся с помощью подпрограммы преобразования данных PyString_FromString().
pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc – новая ссылка */
if (pFunc && PyCallable_Check(pFunc)) {
...
}
Py_XDECREF(pFunc);
После загрузки сценария нужное имя извлекается с помощью PyObject_GetAttrString(). Если имя существует и возвращённый объект является вызываемым, можно считать, что это функция. Затем программа обычным образом формирует кортеж аргументов. После этого вызывается функция Python:
pValue = PyObject_CallObject(pFunc, pArgs);
После возврата из функции pValue может быть NULL или содержать ссылку на возвращаемое значение функции. Не забудьте освободить ссылку после проверки значения.
5.4. Расширение встроенного Python¶Extending Embedded Python
До сих пор встроенный интерпретатор Python не имел доступа к функциональности самого приложения. API Python позволяет это сделать, расширяя встроенный интерпретатор. То есть встроенный интерпретатор дополняется процедурами, предоставляемыми приложением. Хотя это звучит сложно, на самом деле всё не так плохо. Просто нужно на время забыть, что приложение запускает интерпретатор Python. Вместо этого следует рассматривать приложение как набор подпрограмм и написать связующий код, который даёт Python доступ к этим подпрограммам, точно так же, как пишется обычное расширение Python. Например:
static int numargs=0;
/* Возвращает количество аргументов командной строки приложения */
static PyObject*
emb_numargs(PyObject *self, PyObject *args)
{
if(!PyArg_ParseTuple(args, ":numargs"))
return NULL;
return Py_BuildValue("i", numargs);
}
static PyMethodDef EmbMethods[] = {
{"numargs", emb_numargs, METH_VARARGS,
"Return the number of arguments received by the process."},
{NULL, NULL, 0, NULL}
};
Вставьте приведённый выше код непосредственно перед функцией main(). Кроме того, вставьте следующие два оператора сразу после Py_Initialize():
numargs = argc;
Py_InitModule("emb", EmbMethods);
Эти две строки инициализируют переменную numargs и делают функцию emb.numargs() доступной для встроенного интерпретатора Python. Благодаря этим расширениям сценарий Python может делать, например,
import emb
print "Number of arguments", emb.numargs()
В реальном приложении методы будут предоставлять Python API приложения.
5.5. Встраивание Python в C++¶Embedding Python in C++
Также возможно встроить Python в программу на C++; точный способ зависит от деталей используемой системы C++; в общем случае нужно написать основную программу на C++ и использовать компилятор C++ для компиляции и сборки программы. Нет необходимости перекомпилировать сам Python с помощью C++.
5.6. Требования к компоновке¶Linking Requirements
Хотя сценарий configure, поставляемый с исходными кодами Python, правильно собирает Python для экспорта символов, необходимых динамически подключаемым расширениям, это автоматически не наследуется приложениями, встраивающими библиотеку Python статически, по крайней мере в Unix. Проблема возникает, когда приложение компонуется со статической библиотекой времени выполнения (libpython.a) и ему необходимо загружать динамические расширения (реализованные как файлы .so).
Проблема в том, что некоторые точки входа определяются средой выполнения Python исключительно для использования модулями расширения. Если встраивающее приложение не использует ни одну из этих точек входа, некоторые компоновщики не включат эти записи в таблицу символов готового исполняемого файла. Требуются дополнительные опции, чтобы указать компоновщику не удалять эти символы.
Определить правильные опции для конкретной платформы может быть довольно сложно, но, к счастью, конфигурация Python уже содержит эти значения. Чтобы получить их из установленного интерпретатора Python, запустите интерактивный интерпретатор и выполните короткий сеанс, подобный этому:
>>> import distutils.sysconfig
>>> distutils.sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'
Содержимое отображаемой строки – это опции, которые следует использовать. Если строка пуста, дополнительные опции не требуются. Определение LINKFORSHARED соответствует одноимённой переменной в корневом Makefile Python.