embedding.md
1> **Источник:** https://python-all.ru/3.4/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.4/c-api/init.html#c.Py_Initialize). При желании можно передать интерпретатору аргументы командной строки. Затем из любой части приложения можно вызывать интерпретатор.1415Существует несколько способов вызвать интерпретатор: можно передать строку с выражениями Python в [`PyRun_SimpleString()`](https://python-all.ru/3.4/c-api/veryhigh.html#c.PyRun_SimpleString), или передать указатель на файл stdio и имя файла (только для идентификации в сообщениях об ошибках) в [`PyRun_SimpleFile()`](https://python-all.ru/3.4/c-api/veryhigh.html#c.PyRun_SimpleFile). Также можно вызывать низкоуровневые операции, описанные в предыдущих главах, для создания и использования объектов Python.1617> **См. также**18>19> **[*Справочное руководство по Python/C API*](https://python-all.ru/3.4/c-api/index.html#c-api-index)**20>21> Подробности C-интерфейса Python приведены в этом руководстве. Много необходимой информации можно найти здесь.2223## 1.1. Встраивание на очень высоком уровне2425Простейшая форма встраивания Python – использование интерфейса сверхвысокого уровня. Этот интерфейс предназначен для выполнения Python-скрипта без необходимости взаимодействовать с приложением напрямую. Его можно использовать, например, для выполнения некоторой операции над файлом.2627```c28#include <Python.h>2930int31main(int argc, char *argv[])32{33 Py_SetProgramName(argv[0]); /* опционально, но рекомендуется */34 Py_Initialize();35 PyRun_SimpleString("from time import time,ctime\n"36 "print('Today is', ctime(time()))\n");37 Py_Finalize();38 return 0;39}40```4142Функцию [`Py_SetProgramName()`](https://python-all.ru/3.4/c-api/init.html#c.Py_SetProgramName) следует вызывать до [`Py_Initialize()`](https://python-all.ru/3.4/c-api/init.html#c.Py_Initialize), чтобы сообщить интерпретатору пути к библиотекам времени выполнения Python. Затем интерпретатор Python инициализируется вызовом [`Py_Initialize()`](https://python-all.ru/3.4/c-api/init.html#c.Py_Initialize), после чего выполняется жёстко заданный сценарий Python, который выводит дату и время. Далее вызов [`Py_Finalize()`](https://python-all.ru/3.4/c-api/init.html#c.Py_Finalize) завершает работу интерпретатора, после чего программа завершается. В реальной программе сценарий Python может поступать из другого источника, например из текстового редактора, файла или базы данных. Получение кода Python из файла проще выполнить с помощью функции [`PyRun_SimpleFile()`](https://python-all.ru/3.4/c-api/veryhigh.html#c.PyRun_SimpleFile), которая избавляет от необходимости выделять память и загружать содержимое файла.4344## 1.2. За пределами очень высокого уровня встраивания: обзор4546Высокоуровневый интерфейс позволяет выполнять произвольные фрагменты Python-кода из вашего приложения, но обмен значениями данных – как минимум весьма затруднителен. Если это необходимо, следует использовать низкоуровневые вызовы. Ценой написания большего количества кода на C можно достичь почти всего.4748Следует отметить, что расширение Python и встраивание Python – по сути одно и то же действие, несмотря на разные цели. Большинство тем, рассмотренных в предыдущих главах, остаются актуальными. Чтобы показать это, рассмотрим, что на самом деле делает код расширения от Python к C:49501. Преобразование значений данных из Python в C,512. Вызов функции C с использованием преобразованных значений и523. Преобразование значений данных из вызова из C в Python.5354При встраивании Python код интерфейса делает:55561. Преобразование значений данных из C в Python,572. Вызов функции интерфейса Python с использованием преобразованных значений и583. Преобразование значений данных из вызова из Python в C.5960Как видите, шаги преобразования данных просто меняются местами для соответствия разному направлению межъязыковой передачи. Единственное отличие – это функция, которую вызывают между двумя преобразованиями данных. При расширении вызывается C-функция, при встраивании – Python-функция.6162В этой главе не будет обсуждаться преобразование данных из Python в C и обратно. Также предполагается, что правильное использование ссылок и обработка ошибок понятны. Поскольку эти аспекты не отличаются от расширения интерпретатора, необходимые сведения можно найти в предыдущих главах.6364## 1.3. Чистое встраивание6566Первая программа предназначена для выполнения функции из скрипта Python. Как и в разделе об интерфейсе очень высокого уровня, интерпретатор Python не взаимодействует напрямую с приложением (но это изменится в следующем разделе).6768Код для выполнения функции, определённой в скрипте Python:6970```c71#include <Python.h>7273int74main(int argc, char *argv[])75{76 PyObject *pName, *pModule, *pDict, *pFunc;77 PyObject *pArgs, *pValue;78 int i;7980 if (argc < 3) {81 fprintf(stderr,"Usage: call pythonfile funcname [args]\n");82 return 1;83 }8485 Py_Initialize();86 pName = PyUnicode_FromString(argv[1]);87 /* Проверка ошибок pName опущена */8889 pModule = PyImport_Import(pName);90 Py_DECREF(pName);9192 if (pModule != NULL) {93 pFunc = PyObject_GetAttrString(pModule, argv[2]);94 /* pFunc – новая ссылка */9596 if (pFunc && PyCallable_Check(pFunc)) {97 pArgs = PyTuple_New(argc - 3);98 for (i = 0; i < argc - 3; ++i) {99 pValue = PyLong_FromLong(atoi(argv[i + 3]));100 if (!pValue) {101 Py_DECREF(pArgs);102 Py_DECREF(pModule);103 fprintf(stderr, "Cannot convert argument\n");104 return 1;105 }106 /* Ссылка pValue перехвачена здесь: */107 PyTuple_SetItem(pArgs, i, pValue);108 }109 pValue = PyObject_CallObject(pFunc, pArgs);110 Py_DECREF(pArgs);111 if (pValue != NULL) {112 printf("Result of call: %ld\n", PyLong_AsLong(pValue));113 Py_DECREF(pValue);114 }115 else {116 Py_DECREF(pFunc);117 Py_DECREF(pModule);118 PyErr_Print();119 fprintf(stderr,"Call failed\n");120 return 1;121 }122 }123 else {124 if (PyErr_Occurred())125 PyErr_Print();126 fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);127 }128 Py_XDECREF(pFunc);129 Py_DECREF(pModule);130 }131 else {132 PyErr_Print();133 fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);134 return 1;135 }136 Py_Finalize();137 return 0;138}139```140141Этот код загружает сценарий Python с помощью `argv[1]` и вызывает функцию, имя которой указано в `argv[2]`. Её целочисленные аргументы – остальные значения массива `argv`. Если вы [*скомпилируете и скомпонуете*](https://python-all.ru/3.4/extending/embedding.html#compiling) эту программу (назовём готовый исполняемый файл **call**) и используете её для выполнения сценария Python, например:142143```python144def multiply(a,b):145 print("Will compute", a, "times", b)146 c = 0147 for i in range(0, a):148 c = c + b149 return c150```151152то результат будет следующим:153154```c155$ call multiply multiply 3 2156Will compute 3 times 2157Result of call: 6158```159160Хотя программа довольно велика для своей функциональности, большая часть кода отвечает за преобразование данных между Python и C и за сообщения об ошибках. Интересная часть, касающаяся встраивания Python, начинается с161162```c163Py_Initialize();164pName = PyUnicode_FromString(argv[1]);165/* Проверка ошибок pName опущена */166pModule = PyImport_Import(pName);167```168169После инициализации интерпретатора сценарий загружается с помощью [`PyImport_Import()`](https://python-all.ru/3.4/c-api/import.html#c.PyImport_Import). Эта функция принимает строку Python в качестве аргумента; строка создаётся с помощью функции преобразования данных [`PyUnicode_FromString()`](https://python-all.ru/3.4/c-api/unicode.html#c.PyUnicode_FromString).170171```c172pFunc = PyObject_GetAttrString(pModule, argv[2]);173/* pFunc – новая ссылка */174175if (pFunc && PyCallable_Check(pFunc)) {176 ...177}178Py_XDECREF(pFunc);179```180181После загрузки сценария нужное имя извлекается с помощью [`PyObject_GetAttrString()`](https://python-all.ru/3.4/c-api/object.html#c.PyObject_GetAttrString). Если имя существует и возвращённый объект является вызываемым, можно считать, что это функция. Затем программа обычным образом формирует кортеж аргументов. После этого вызывается функция Python:182183```c184pValue = PyObject_CallObject(pFunc, pArgs);185```186187После возврата из функции `pValue` может быть *NULL* или содержать ссылку на возвращаемое значение функции. Не забудьте освободить ссылку после проверки значения.188189## 1.4. Расширение встроенного Python190191До сих пор встроенный интерпретатор Python не имел доступа к функциональности самого приложения. API Python позволяет это сделать, расширяя встроенный интерпретатор. То есть встроенный интерпретатор дополняется процедурами, предоставляемыми приложением. Хотя это звучит сложно, на самом деле всё не так плохо. Просто нужно на время забыть, что приложение запускает интерпретатор Python. Вместо этого следует рассматривать приложение как набор подпрограмм и написать связующий код, который даёт Python доступ к этим подпрограммам, точно так же, как пишется обычное расширение Python. Например:192193```c194static int numargs=0;195196/* Возвращает количество аргументов командной строки приложения */197static PyObject*198emb_numargs(PyObject *self, PyObject *args)199{200 if(!PyArg_ParseTuple(args, ":numargs"))201 return NULL;202 return PyLong_FromLong(numargs);203}204205static PyMethodDef EmbMethods[] = {206 {"numargs", emb_numargs, METH_VARARGS,207 "Return the number of arguments received by the process."},208 {NULL, NULL, 0, NULL}209};210211static PyModuleDef EmbModule = {212 PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,213 NULL, NULL, NULL, NULL214};215216static PyObject*217PyInit_emb(void)218{219 return PyModule_Create(&EmbModule);220}221```222223Вставьте приведённый выше код непосредственно перед функцией `main()`. Также вставьте следующие два оператора перед вызовом [`Py_Initialize()`](https://python-all.ru/3.4/c-api/init.html#c.Py_Initialize):224225```c226numargs = argc;227PyImport_AppendInittab("emb", &PyInit_emb);228```229230Эти две строки инициализируют переменную `numargs` и делают функцию `emb.numargs()` доступной для встроенного интерпретатора Python. Благодаря этим расширениям сценарий Python может делать, например,231232```python233import emb234print("Number of arguments", emb.numargs())235```236237В реальном приложении методы будут предоставлять Python API приложения.238239## 1.5. Встраивание Python в C++240241Также возможно встроить Python в программу на C++; точный способ зависит от деталей используемой системы C++; в общем случае нужно написать основную программу на C++ и использовать компилятор C++ для компиляции и сборки программы. Нет необходимости перекомпилировать сам Python с помощью C++.242243## 1.6. Компиляция и компоновка в Unix-подобных системах244245Подобрать правильные флаги для компилятора (и компоновщика), чтобы встроить интерпретатор Python в приложение, бывает непросто, особенно потому, что Python должен загружать библиотечные модули, реализованные как динамические расширения C (файлы `.so`), слинкованные с ним.246247Чтобы узнать необходимые флаги компилятора и компоновщика, можно выполнить сценарий `pythonX.Y-config`, который создаётся в процессе установки (также может быть доступен сценарий `python3-config`). У этого сценария есть несколько опций; следующие будут непосредственно полезны:248249- `pythonX.Y-config --cflags` выдаст рекомендуемые флаги при компиляции:250251 ```c252 $ /opt/bin/python3.4-config --cflags253 -I/opt/include/python3.4m -I/opt/include/python3.4m -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes254 ```255- `pythonX.Y-config --ldflags` выдаст рекомендуемые флаги при компоновке:256257 ```c258 $ /opt/bin/python3.4-config --ldflags259 -L/opt/lib/python3.4/config-3.4m -lpthread -ldl -lutil -lm -lpython3.4m -Xlinker -export-dynamic260 ```261262> **Примечание**263>264> Чтобы избежать путаницы между несколькими установками Python (и особенно между системным Python и собственноручно скомпилированным Python), рекомендуется использовать абсолютный путь к `pythonX.Y-config`, как в приведённом выше примере.265266Если эта процедура не работает (она не гарантированно работает на всех Unix-подобных платформах; однако мы приветствуем [*сообщения об ошибках*](https://python-all.ru/3.4/bugs.html#reporting-bugs)), вам придётся изучить системную документацию по динамической компоновке и/или исследовать `Makefile` Python (используйте [`sysconfig.get_makefile_filename()`](https://python-all.ru/3.4/library/sysconfig.html#sysconfig.get_makefile_filename), чтобы найти его расположение) и параметры компиляции. В этом случае модуль [`sysconfig`](https://python-all.ru/3.4/library/sysconfig.html#module-sysconfig) – полезный инструмент для программного извлечения значений конфигурации, которые нужно объединить. Например:267268```python269>>> import sysconfig270>>> sysconfig.get_config_var('LIBS')271'-lpthread -ldl -lutil'272>>> sysconfig.get_config_var('LINKFORSHARED')273'-Xlinker -export-dynamic'274```275