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

В предыдущих главах обсуждалось расширение Python, то есть расширение функциональности Python путем присоединения к нему библиотеки функций языка Си. Можно сделать и наоборот: обогатить ваше приложение на 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

Подробности интерфейса 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. Если вы compile and link эту программу (назовем готовый исполняемый файл 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.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-подобных платформ; однако мы приветствуем bug reports), вам придется прочитать документацию вашей системы о динамическом связывании и/или изучить Makefile (используйте sysconfig.get_makefile_filename(), чтобы найти его расположение) и параметры компиляции Python. В этом случае модуль sysconfig является полезным инструментом для программного извлечения значений конфигурации, которые вы захотите объединить вместе. Например:

>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl  -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'
Вернуться на верх