1. Встраивание 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);  /* необязательно, но рекомендуется */
    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 на 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]);
/* Ошибка проверки pName исключена */
pModule = PyImport_Import(pName);

После инициализации интерпретатора сценарий загружается с помощью PyImport_Import(). Эта подпрограмма нуждается в Python строке в качестве аргумента, который создается с использованием подпрограммы преобразования PyUnicode_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, либо содержит ссылку на возвращаемом значением функции. Обязательно освободите ссылку после проверки значения.

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

>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl  -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'