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.

Дивись також

Довідковий посібник з API Python/C

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

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 не мав доступу до функціональних можливостей самої програми. Python API дозволяє це, розширюючи вбудований інтерпретатор. Тобто вбудований інтерпретатор розширюється підпрограмами, наданими програмою. Хоча це звучить складно, це не так вже й погано. Просто забудьте на деякий час, що програма запускає інтерпретатор 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);

These two lines initialize the numargs variable, and make the emb.numargs() function accessible to the embedded Python interpreter. With these extensions, the Python script can do things like

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 will give you the recommended flags when linking:

    $ /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'