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

embedding.md

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

1> **Источник:** https://python-all.ru/3.0/extending/embedding.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Встраивание 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.0/c-api/init.html#Py_Initialize). Существуют дополнительные вызовы для передачи аргументов командной строки в Python. После этого интерпретатор можно вызывать из любой части приложения.1415Существует несколько способов вызвать интерпретатор: можно передать строку с инструкциями Python в [`PyRun_SimpleString`](https://python-all.ru/3.0/c-api/veryhigh.html#PyRun_SimpleString), или передать указатель на файл stdio и имя файла (только для идентификации в сообщениях об ошибках) в [`PyRun_SimpleFile`](https://python-all.ru/3.0/c-api/veryhigh.html#PyRun_SimpleFile). Также можно вызывать низкоуровневые операции, описанные в предыдущих главах, для создания и использования объектов Python.1617Простая демонстрация встраивания Python находится в каталоге `Demo/embed/` дистрибутива исходных кодов.1819> **См. также**20>21> **[*Справочное руководство по Python/C API*](https://python-all.ru/3.0/c-api/index.html#c-api-index)**22>23> Подробности C-интерфейса Python приведены в этом руководстве. Много необходимой информации можно найти здесь.2425## Встраивание на сверхвысоком уровне2627Простейшая форма встраивания Python – использование интерфейса сверхвысокого уровня. Этот интерфейс предназначен для выполнения Python-скрипта без необходимости взаимодействовать с приложением напрямую. Его можно использовать, например, для выполнения некоторой операции над файлом.2829```c30#include <Python.h>3132int33main(int argc, char *argv[])34{35  Py_Initialize();36  PyRun_SimpleString("from time import time,ctime\n"37                     "print('Today is', ctime(time()))\n");38  Py_Finalize();39  return 0;40}41```4243Приведённый выше код сначала инициализирует интерпретатор Python с помощью [`Py_Initialize`](https://python-all.ru/3.0/c-api/init.html#Py_Initialize), затем выполняет жёстко заданный скрипт Python, который выводит дату и время. После этого вызов [`Py_Finalize`](https://python-all.ru/3.0/c-api/init.html#Py_Finalize) завершает работу интерпретатора, и программа заканчивается. В реальной программе можно получить скрипт Python из другого источника, например, из редактора текста, файла или базы данных. Получение кода Python из файла лучше выполнять с помощью функции [`PyRun_SimpleFile`](https://python-all.ru/3.0/c-api/veryhigh.html#PyRun_SimpleFile), что избавляет от необходимости выделять память и загружать содержимое файла.4445## За пределами сверхвысокого уровня: обзор4647Высокоуровневый интерфейс позволяет выполнять произвольные фрагменты Python-кода из вашего приложения, но обмен значениями данных – как минимум весьма затруднителен. Если это необходимо, следует использовать низкоуровневые вызовы. Ценой написания большего количества кода на C можно достичь почти всего.4849Следует отметить, что расширение Python и встраивание Python – по сути одно и то же действие, несмотря на разные цели. Большинство тем, рассмотренных в предыдущих главах, остаются актуальными. Чтобы показать это, рассмотрим, что на самом деле делает код расширения от Python к C:50511. Преобразование значений данных из Python в C,522. Вызов функции C с использованием преобразованных значений и533. Преобразование значений данных из вызова из C в Python.5455При встраивании Python код интерфейса делает:56571. Преобразование значений данных из C в Python,582. Вызов функции интерфейса Python с использованием преобразованных значений и593. Преобразование значений данных из вызова из Python в C.6061Как видите, шаги преобразования данных просто меняются местами для соответствия разному направлению межъязыковой передачи. Единственное отличие – это функция, которую вызывают между двумя преобразованиями данных. При расширении вызывается C-функция, при встраивании – Python-функция.6263В этой главе не будет обсуждаться преобразование данных из Python в C и обратно. Также предполагается, что правильное использование ссылок и обработка ошибок понятны. Поскольку эти аспекты не отличаются от расширения интерпретатора, необходимые сведения можно найти в предыдущих главах.6465## Чистое встраивание6667Первая программа предназначена для выполнения функции из скрипта Python. Как и в разделе об интерфейсе очень высокого уровня, интерпретатор Python не взаимодействует напрямую с приложением (но это изменится в следующем разделе).6869Код для выполнения функции, определённой в скрипте Python:7071```c72#include <Python.h>7374int75main(int argc, char *argv[])76{77    PyObject *pName, *pModule, *pDict, *pFunc;78    PyObject *pArgs, *pValue;79    int i;8081    if (argc < 3) {82        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");83        return 1;84    }8586    Py_Initialize();87    pName = PyUnicode_FromString(argv[1]);88    /* Проверка ошибок pName опущена */8990    pModule = PyImport_Import(pName);91    Py_DECREF(pName);9293    if (pModule != NULL) {94        pFunc = PyObject_GetAttrString(pModule, argv[2]);95        /* pFunc – новая ссылка */9697        if (pFunc && PyCallable_Check(pFunc)) {98            pArgs = PyTuple_New(argc - 3);99            for (i = 0; i < argc - 3; ++i) {100                pValue = PyLong_FromLong(atoi(argv[i + 3]));101                if (!pValue) {102                    Py_DECREF(pArgs);103                    Py_DECREF(pModule);104                    fprintf(stderr, "Cannot convert argument\n");105                    return 1;106                }107                /* Ссылка pValue перехвачена здесь: */108                PyTuple_SetItem(pArgs, i, pValue);109            }110            pValue = PyObject_CallObject(pFunc, pArgs);111            Py_DECREF(pArgs);112            if (pValue != NULL) {113                printf("Result of call: %ld\n", PyLong_AsLong(pValue));114                Py_DECREF(pValue);115            }116            else {117                Py_DECREF(pFunc);118                Py_DECREF(pModule);119                PyErr_Print();120                fprintf(stderr,"Call failed\n");121                return 1;122            }123        }124        else {125            if (PyErr_Occurred())126                PyErr_Print();127            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);128        }129        Py_XDECREF(pFunc);130        Py_DECREF(pModule);131    }132    else {133        PyErr_Print();134        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);135        return 1;136    }137    Py_Finalize();138    return 0;139}140```141142Этот код загружает скрипт Python с помощью `argv[1]` и вызывает функцию, указанную в `argv[2]`. Целочисленные аргументы – это остальные значения массива `argv`. Если скомпилировать и слинковать эту программу (назовём готовый исполняемый файл **call**) и использовать её для выполнения скрипта Python, например:143144```c145def multiply(a,b):146    print("Will compute", a, "times", b)147    c = 0148    for i in range(0, a):149        c = c + b150    return c151```152153то результат будет следующим:154155```c156$ call multiply multiply 3 2157Will compute 3 times 2158Result of call: 6159```160161Хотя программа довольно велика для своей функциональности, большая часть кода отвечает за преобразование данных между Python и C и за сообщения об ошибках. Интересная часть, касающаяся встраивания Python, начинается с162163```c164Py_Initialize();165pName = PyString_FromString(argv[1]);166/* Проверка ошибок pName опущена */167pModule = PyImport_Import(pName);168```169170После инициализации интерпретатора скрипт загружается с помощью [`PyImport_Import`](https://python-all.ru/3.0/c-api/import.html#PyImport_Import). Эта функция требует в качестве аргумента строку Python, которая создаётся с помощью функции преобразования данных `PyString_FromString`.171172```c173pFunc = PyObject_GetAttrString(pModule, argv[2]);174/* pFunc – новая ссылка */175176if (pFunc && PyCallable_Check(pFunc)) {177    ...178}179Py_XDECREF(pFunc);180```181182Когда скрипт загружен, нужное имя извлекается с помощью [`PyObject_GetAttrString`](https://python-all.ru/3.0/c-api/object.html#PyObject_GetAttrString). Если имя существует и возвращённый объект является вызываемым, можно считать, что это функция. Затем программа обычным образом создаёт кортеж аргументов. Вызов функции Python выполняется с помощью:183184```c185pValue = PyObject_CallObject(pFunc, pArgs);186```187188После возврата из функции `pValue` может быть *NULL* или содержать ссылку на возвращаемое значение функции. Не забудьте освободить ссылку после проверки значения.189190## Расширение встроенного Python191192До сих пор встроенный интерпретатор Python не имел доступа к функциональности самого приложения. API Python позволяет это сделать, расширяя встроенный интерпретатор. То есть встроенный интерпретатор дополняется процедурами, предоставляемыми приложением. Хотя это звучит сложно, на самом деле всё не так плохо. Просто нужно на время забыть, что приложение запускает интерпретатор Python. Вместо этого следует рассматривать приложение как набор подпрограмм и написать связующий код, который даёт Python доступ к этим подпрограммам, точно так же, как пишется обычное расширение Python. Например:193194```c195static int numargs=0;196197/* Возвращает количество аргументов командной строки приложения */198static PyObject*199emb_numargs(PyObject *self, PyObject *args)200{201    if(!PyArg_ParseTuple(args, ":numargs"))202        return NULL;203    return Py_BuildValue("i", numargs);204}205206static PyMethodDef EmbMethods[] = {207    {"numargs", emb_numargs, METH_VARARGS,208     "Return the number of arguments received by the process."},209    {NULL, NULL, 0, NULL}210};211212static PyModuleDef EmbModule = {213    PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,214    NULL, NULL, NULL, NULL215};216217static PyObject*218PyInit_emb(void)219{220    return PyModule_Create(&EmbModule);221}222```223224Вставьте приведённый выше код непосредственно перед функцией `main`. Также вставьте следующие два оператора перед вызовом [`Py_Initialize`](https://python-all.ru/3.0/c-api/init.html#Py_Initialize):225226```c227numargs = argc;228PyImport_AppendInittab("emb", &PyInit_emb);229```230231Эти две строки инициализируют переменную `numargs` и делают функцию `emb.numargs()` доступной для встроенного интерпретатора Python. Благодаря этим расширениям сценарий Python может делать, например,232233```c234import emb235print("Number of arguments", emb.numargs())236```237238В реальном приложении методы будут предоставлять Python API приложения.239240## Встраивание Python в C++241242Также возможно встроить Python в программу на C++; точный способ зависит от деталей используемой системы C++; в общем случае нужно написать основную программу на C++ и использовать компилятор C++ для компиляции и сборки программы. Нет необходимости перекомпилировать сам Python с помощью C++.243244## Требования к компоновке245246Хотя сценарий **configure**, поставляемый с исходными кодами Python, правильно собирает Python для экспорта символов, необходимых динамически подключаемым расширениям, это автоматически не наследуется приложениями, встраивающими библиотеку Python статически, по крайней мере в Unix. Проблема возникает, когда приложение компонуется со статической библиотекой времени выполнения (`libpython.a`) и ему необходимо загружать динамические расширения (реализованные как файлы `.so`).247248Проблема в том, что некоторые точки входа определяются средой выполнения Python исключительно для использования модулями расширения. Если встраивающее приложение не использует ни одну из этих точек входа, некоторые компоновщики не включат эти записи в таблицу символов готового исполняемого файла. Требуются дополнительные опции, чтобы указать компоновщику не удалять эти символы.249250Определить правильные опции для конкретной платформы может быть довольно сложно, но, к счастью, конфигурация Python уже содержит эти значения. Чтобы получить их из установленного интерпретатора Python, запустите интерактивный интерпретатор и выполните короткий сеанс, подобный этому:251252```c253>>> import distutils.sysconfig254>>> distutils.sysconfig.get_config_var('LINKFORSHARED')255'-Xlinker -export-dynamic'256```257258Содержимое отображаемой строки – это опции, которые следует использовать. Если строка пуста, дополнительные опции не требуются. Определение `LINKFORSHARED` соответствует одноимённой переменной в корневом `Makefile` Python.259