Spec-Zone.ru › Python 3.10

Встраивание 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

Подробное описание интерфейса Python на C приведено в данном руководстве. Здесь можно найти много необходимой информации.

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 действительно делает:

  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_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.10/extending/embedding.html

Spec-Zone.ru

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