Spec-Zone.ru › Python 3.14

Встраивание Python в другое приложение

В предыдущих главах обсуждалось расширение Python, то есть добавление к нему библиотеки функций на C для расширения его возможностей. Можно поступить и наоборот: расширить ваше приложение на C/C++, встроив в него Python. Встраивание позволяет реализовать часть функциональности приложения на Python, а не на C или C++. Это может пригодиться для самых разных целей; например, пользователи смогут настраивать приложение под свои нужды, написав скрипты на 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[])
{
    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 использовался, чтобы указать, что в некоторых API следует использовать Py_ssize_t вместо int. Начиная с Python 3.13 в этом больше нет необходимости, но мы оставляем его здесь для обеспечения обратной совместимости. Описание этого макроса см. в разделе Строки и буферы.

Для передачи интерпретатору путей к библиотекам времени выполнения Python перед вызовом Py_InitializeFromConfig() следует задать PyConfig.program_name. Затем интерпретатор Python инициализируется с помощью Py_Initialize(), после чего выполняется жёстко заданный скрипт Python, который выводит дату и время. Затем вызов Py_FinalizeEx() завершает работу интерпретатора, а вслед за ним завершается и программа. В реальной программе скрипт Python можно получать из другого источника, например из процедуры текстового редактора, файла или базы данных. Для получения кода Python из файла удобнее использовать функцию PyRun_SimpleFile(), которая избавляет от необходимости выделять память и загружать содержимое файла.

1.2. За пределами встраивания с помощью интерфейса очень высокого уровня: обзор

Интерфейс высокого уровня позволяет выполнять произвольные фрагменты кода Python из приложения, однако обмен значениями данных, мягко говоря, довольно неудобен. Если вам это необходимо, используйте низкоуровневые вызовы. Придётся написать больше кода на C, зато вы сможете добиться практически всего.

Следует отметить, что расширение Python и его встраивание — по сути одна и та же задача, хотя цели у них разные. Большинство тем, рассмотренных в предыдущих главах, по-прежнему актуальны. Чтобы это показать, рассмотрим, что на самом деле делает код расширения при передаче данных из Python в C:

  1. Преобразует значения данных из Python в C,
  2. Вызывает функцию на C, передавая ей преобразованные значения, и
  3. Преобразует значения данных, полученные в результате вызова на C, в Python.

При встраивании Python интерфейсный код выполняет следующие действия:

  1. Преобразует значения данных из C в Python,
  2. Вызывает подпрограмму интерфейса на Python, передавая ей преобразованные значения, и
  3. Преобразует значения данных, полученные в результате вызова на 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_DecodeFSDefault().

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 emb_module_methods[] = {
    {"numargs", emb_numargs, METH_VARARGS,
     "Return the number of arguments received by the process."},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef emb_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "emb",
    .m_size = 0,
    .m_methods = emb_module_methods,
};

static PyObject*
PyInit_emb(void)
{
    return PyModuleDef_Init(&emb_module);
}

Вставьте приведённый выше код непосредственно перед функцией 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 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/extending/embedding.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API