Документация Python неофициальный перевод

embedding.md

305 строк · 22.4 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3/extending/embedding.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# 1. Встраивание Python в другое приложение89В предыдущих главах обсуждалось, как расширять Python, то есть как расширить функциональность Python, подключая к нему библиотеку на C. Также можно сделать наоборот: обогатить своё приложение на C/C++ путём встраивания в него Python. Встраивание даёт вашему приложению возможность реализовать часть функциональности приложения на Python, а не на C или C++. Это можно использовать для многих целей; один из примеров – позволить пользователям настраивать приложение под свои нужды, написав несколько скриптов на Python. Вы также можете использовать это сами, если какую-то функциональность проще написать на Python более легко.1011Разница в том, что при расширении Python главная программа приложения остаётся интерпретатором Python, тогда как при встраивании Python главная программа может не иметь ничего общего с Python – вместо этого некоторые части приложения время от времени вызывают интерпретатор Python для выполнения некоторого кода на Python.1213Итак, если вы встраиваете Python, вы предоставляете собственную главную программу. Одна из задач этой главной программы – инициализировать интерпретатор Python. Как минимум, необходимо вызвать функцию [`Py_Initialize()`](https://python-all.ru/3/c-api/interp-lifecycle.html#c.Py_Initialize). Существуют необязательные вызовы для передачи аргументов командной строки Python. Затем позже можно вызывать интерпретатор из любой части приложения.1415Существует несколько различных способов вызова интерпретатора: можно передать строку содержащую операторы Python в [`PyRun_SimpleString()`](https://python-all.ru/3/c-api/veryhigh.html#c.PyRun_SimpleString), или можно передать указатель файла stdio и имя файла (только для идентификации в сообщениях об ошибках) в [`PyRun_SimpleFile()`](https://python-all.ru/3/c-api/veryhigh.html#c.PyRun_SimpleFile). Также можно вызывать низкоуровневые операции, описанные в предыдущих главах, для создания и использования объектов Python.1617> **См. также**18>19> **[Справочное руководство по Python/C API](https://python-all.ru/3/c-api/index.html#c-api-index)**20>21> Подробности C-интерфейса Python приведены в этом руководстве. Много необходимой информации можно найти здесь.2223## 1.1. Встраивание на очень высоком уровне2425Простейшая форма встраивания Python – использование интерфейса сверхвысокого уровня. Этот интерфейс предназначен для выполнения Python-скрипта без необходимости взаимодействовать с приложением напрямую. Его можно использовать, например, для выполнения некоторой операции над файлом.2627```c28#define PY_SSIZE_T_CLEAN29#include <Python.h>3031int32main(int argc, char *argv[])33{34    PyStatus status;35    PyConfig config;36    PyConfig_InitPythonConfig(&config);3738    /* опционально, но рекомендуется */39    status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);40    if (PyStatus_Exception(status)) {41        goto exception;42    }4344    status = Py_InitializeFromConfig(&config);45    if (PyStatus_Exception(status)) {46        goto exception;47    }48    PyConfig_Clear(&config);4950    PyRun_SimpleString("from time import time,ctime\n"51                       "print('Today is', ctime(time()))\n");52    if (Py_FinalizeEx() < 0) {53        exit(120);54    }55    return 0;5657  exception:58     PyConfig_Clear(&config);59     Py_ExitStatusException(status);60}61```6263> **Примечание**64>65> `#define PY_SSIZE_T_CLEAN` использовался для указания того, что `Py_ssize_t` должен использоваться в некоторых API вместо `int`. Он не требуется начиная с Python 3.13, но мы сохраняем его здесь для обратной совместимости. См. [Строки и буферы](https://python-all.ru/3/c-api/arg.html#arg-parsing-string-and-buffers) для описания этого макроса.6667Вызов [`PyConfig.program_name`](https://python-all.ru/3/c-api/init_config.html#c.PyConfig.program_name) должен выполняться до [`Py_InitializeFromConfig()`](https://python-all.ru/3/c-api/interp-lifecycle.html#c.Py_InitializeFromConfig), чтобы сообщить интерпретатору о путях к библиотекам времени выполнения Python. Далее интерпретатор Python инициализируется с помощью [`Py_Initialize()`](https://python-all.ru/3/c-api/interp-lifecycle.html#c.Py_Initialize), после чего выполняется жёстко запрограммированный Python-скрипт, который выводит дату и время. Затем вызов [`Py_FinalizeEx()`](https://python-all.ru/3/c-api/interp-lifecycle.html#c.Py_FinalizeEx) завершает работу интерпретатора, после чего программа завершается. В реальной программе можно получать Python-скрипт из другого источника, например из редактора текста, файла или базы данных. Получение кода Python из файла лучше выполнять с помощью функции [`PyRun_SimpleFile()`](https://python-all.ru/3/c-api/veryhigh.html#c.PyRun_SimpleFile), которая избавляет от необходимости выделять память и загружать содержимое файла.6869## 1.2. За пределами очень высокого уровня: обзор7071Высокоуровневый интерфейс позволяет выполнять произвольные фрагменты Python-кода из вашего приложения, но обмен значениями данных – как минимум весьма затруднителен. Если это необходимо, следует использовать низкоуровневые вызовы. Ценой написания большего количества кода на C можно достичь почти всего.7273Следует отметить, что расширение Python и встраивание Python – по сути одно и то же действие, несмотря на разные цели. Большинство тем, рассмотренных в предыдущих главах, остаются актуальными. Чтобы показать это, рассмотрим, что на самом деле делает код расширения от Python к C:74751. Преобразование значений данных из Python в C,762. Вызов функции C с использованием преобразованных значений и773. Преобразование значений данных из вызова из C в Python.7879При встраивании Python код интерфейса делает:80811. Преобразование значений данных из C в Python,822. Вызов функции интерфейса Python с использованием преобразованных значений и833. Преобразование значений данных из вызова из Python в C.8485Как видите, шаги преобразования данных просто меняются местами для соответствия разному направлению межъязыковой передачи. Единственное отличие – это функция, которую вызывают между двумя преобразованиями данных. При расширении вызывается C-функция, при встраивании – Python-функция.8687В этой главе не будет обсуждаться преобразование данных из Python в C и обратно. Также предполагается, что правильное использование ссылок и обработка ошибок понятны. Поскольку эти аспекты не отличаются от расширения интерпретатора, необходимые сведения можно найти в предыдущих главах.8889## 1.3. Чистое встраивание9091Первая программа предназначена для выполнения функции из скрипта Python. Как и в разделе об интерфейсе очень высокого уровня, интерпретатор Python не взаимодействует напрямую с приложением (но это изменится в следующем разделе).9293Код для выполнения функции, определённой в скрипте Python:9495```c96#define PY_SSIZE_T_CLEAN97#include <Python.h>9899int100main(int argc, char *argv[])101{102    PyObject *pName, *pModule, *pFunc;103    PyObject *pArgs, *pValue;104    int i;105106    if (argc < 3) {107        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");108        return 1;109    }110111    Py_Initialize();112    pName = PyUnicode_DecodeFSDefault(argv[1]);113    /* Проверка ошибок pName опущена */114115    pModule = PyImport_Import(pName);116    Py_DECREF(pName);117118    if (pModule != NULL) {119        pFunc = PyObject_GetAttrString(pModule, argv[2]);120        /* pFunc – новая ссылка */121122        if (pFunc && PyCallable_Check(pFunc)) {123            pArgs = PyTuple_New(argc - 3);124            for (i = 0; i < argc - 3; ++i) {125                pValue = PyLong_FromLong(atoi(argv[i + 3]));126                if (!pValue) {127                    Py_DECREF(pArgs);128                    Py_DECREF(pModule);129                    fprintf(stderr, "Cannot convert argument\n");130                    return 1;131                }132                /* Ссылка pValue перехвачена здесь: */133                PyTuple_SetItem(pArgs, i, pValue);134            }135            pValue = PyObject_CallObject(pFunc, pArgs);136            Py_DECREF(pArgs);137            if (pValue != NULL) {138                printf("Result of call: %ld\n", PyLong_AsLong(pValue));139                Py_DECREF(pValue);140            }141            else {142                Py_DECREF(pFunc);143                Py_DECREF(pModule);144                PyErr_Print();145                fprintf(stderr,"Call failed\n");146                return 1;147            }148        }149        else {150            if (PyErr_Occurred())151                PyErr_Print();152            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);153        }154        Py_XDECREF(pFunc);155        Py_DECREF(pModule);156    }157    else {158        PyErr_Print();159        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);160        return 1;161    }162    if (Py_FinalizeEx() < 0) {163        return 120;164    }165    return 0;166}167```168169Этот код загружает скрипт Python с помощью `argv[1]` и вызывает функцию с именем из `argv[2]`. Его целочисленные аргументы – это остальные значения массива `argv`. Если [скомпилировать и собрать](https://python-all.ru/3/extending/embedding.html#compiling) эту программу (назовём готовый исполняемый файл **call**) и использовать её для выполнения скрипта Python, например:170171```python172def multiply(a,b):173    print("Will compute", a, "times", b)174    c = 0175    for i in range(0, a):176        c = c + b177    return c178```179180то результат будет следующим:181182```console183$ call multiply multiply 3 2184Will compute 3 times 2185Result of call: 6186```187188Хотя программа довольно велика для своей функциональности, большая часть кода отвечает за преобразование данных между Python и C и за сообщения об ошибках. Интересная часть, касающаяся встраивания Python, начинается с189190```c191Py_Initialize();192pName = PyUnicode_DecodeFSDefault(argv[1]);193/* Проверка ошибок pName опущена */194pModule = PyImport_Import(pName);195```196197После инициализации интерпретатора скрипт загружается с помощью [`PyImport_Import()`](https://python-all.ru/3/c-api/import.html#c.PyImport_Import). Эта процедура требует в качестве аргумента строку Python, которая создаётся с помощью функции преобразования данных [`PyUnicode_DecodeFSDefault()`](https://python-all.ru/3/c-api/unicode.html#c.PyUnicode_DecodeFSDefault).198199```c200pFunc = PyObject_GetAttrString(pModule, argv[2]);201/* pFunc – новая ссылка */202203if (pFunc && PyCallable_Check(pFunc)) {204    ...205}206Py_XDECREF(pFunc);207```208209После загрузки скрипта искомое имя извлекается с помощью [`PyObject_GetAttrString()`](https://python-all.ru/3/c-api/object.html#c.PyObject_GetAttrString). Если имя существует и возвращённый объект является вызываемым, можно с уверенностью считать, что это функция. Затем программа обычным образом создаёт кортеж аргументов. После этого выполняется вызов функции Python с помощью:210211```c212pValue = PyObject_CallObject(pFunc, pArgs);213```214215После возврата из функции `pValue` либо равен `NULL`, либо содержит ссылку на возвращаемое значение функции. После проверки значения ссылку необходимо освободить.216217## 1.4. Расширение встроенного Python218219До сих пор встроенный интерпретатор Python не имел доступа к функциональности самого приложения. API Python позволяет это сделать, расширяя встроенный интерпретатор. То есть встроенный интерпретатор дополняется процедурами, предоставляемыми приложением. Хотя это звучит сложно, на самом деле всё не так плохо. Просто нужно на время забыть, что приложение запускает интерпретатор Python. Вместо этого следует рассматривать приложение как набор подпрограмм и написать связующий код, который даёт Python доступ к этим подпрограммам, точно так же, как пишется обычное расширение Python. Например:220221```c222static int numargs=0;223224/* Возвращает количество аргументов командной строки приложения */225static PyObject*226emb_numargs(PyObject *self, PyObject *args)227{228    if(!PyArg_ParseTuple(args, ":numargs"))229        return NULL;230    return PyLong_FromLong(numargs);231}232233static PyMethodDef emb_module_methods[] = {234    {"numargs", emb_numargs, METH_VARARGS,235     "Return the number of arguments received by the process."},236    {NULL, NULL, 0, NULL}237};238239static struct PyModuleDef emb_module = {240    .m_base = PyModuleDef_HEAD_INIT,241    .m_name = "emb",242    .m_size = 0,243    .m_methods = emb_module_methods,244};245246static PyObject*247PyInit_emb(void)248{249    return PyModuleDef_Init(&emb_module);250}251```252253Вставьте приведённый выше код непосредственно перед функцией `main()`. Также вставьте следующие два оператора перед вызовом [`Py_Initialize()`](https://python-all.ru/3/c-api/interp-lifecycle.html#c.Py_Initialize):254255```c256numargs = argc;257PyImport_AppendInittab("emb", &PyInit_emb);258```259260Эти две строки инициализируют переменную `numargs` и делают функцию `emb.numargs()` доступной для встроенного интерпретатора Python. С такими расширениями скрипт Python может делать следующее:261262```python263import emb264print("Number of arguments", emb.numargs())265```266267В реальном приложении методы будут предоставлять Python API приложения.268269## 1.5. Встраивание Python в C++270271Также возможно встроить Python в программу на C++; точный способ зависит от деталей используемой системы C++; в общем случае нужно написать основную программу на C++ и использовать компилятор C++ для компиляции и сборки программы. Нет необходимости перекомпилировать сам Python с помощью C++.272273## 1.6. Компиляция и компоновка в Unix-подобных системах274275Найти правильные флаги для передачи компилятору (и компоновщику) для встраивания интерпретатора Python в приложение – задача не всегда тривиальная, особенно потому что Python должен загружать библиотечные модули, реализованные в виде динамических расширений C (файлы `.so`), слинкованных с ним.276277Чтобы узнать необходимые флаги компилятора и компоновщика, можно выполнить скрипт `pythonX.Y-config`, который создаётся в процессе установки (также может быть доступен скрипт `python3-config`). У этого скрипта есть несколько опций, из которых следующие будут непосредственно полезны:278279- `pythonX.Y-config --cflags` выдаст рекомендуемые флаги при компиляции:280281  ```console282  $ /opt/bin/python3.11-config --cflags283  -I/opt/include/python3.11 -I/opt/include/python3.11 -Wsign-compare  -DNDEBUG -g -fwrapv -O3 -Wall284  ```285- `pythonX.Y-config --ldflags --embed` выдаст рекомендуемые флаги при компоновке:286287  ```console288  $ /opt/bin/python3.11-config --ldflags --embed289  -L/opt/lib/python3.11/config-3.11-x86_64-linux-gnu -L/opt/lib -lpython3.11 -lpthread -ldl  -lutil -lm290  ```291292> **Примечание**293>294> Чтобы избежать путаницы между несколькими установками Python (и особенно между системным Python и собственноручно скомпилированным Python), рекомендуется использовать абсолютный путь к `pythonX.Y-config`, как в примере выше.295296Если эта процедура не работает (она не гарантирует работу на всех Unix-подобных платформах; однако мы [приветствуем сообщения об ошибках](https://python-all.ru/3/bugs.html#reporting-bugs)), то придётся прочитать документацию вашей системы о динамической компоновке и/или изучить файл `Makefile` Python (используйте [`sysconfig.get_makefile_filename()`](https://python-all.ru/3/library/sysconfig.html#sysconfig.get_makefile_filename), чтобы найти его расположение) и параметры компиляции. В этом случае модуль [`sysconfig`](https://python-all.ru/3/library/sysconfig.html#module-sysconfig) – полезный инструмент для программного извлечения значений конфигурации, которые нужно скомбинировать. Например:297298```pycon299>>> import sysconfig300>>> sysconfig.get_config_var('LIBS')301'-lpthread -ldl  -lutil'302>>> sysconfig.get_config_var('LINKFORSHARED')303'-Xlinker -export-dynamic'304```305