Python, встроенный в CPP:как вернуть данные обратно в CPP
-
03-07-2019 - |
Вопрос
Работая над проектом на C ++, я искал стороннюю библиотеку для чего-то, что не является моей основной деятельностью.Я нашел действительно хорошую библиотеку, делающую именно то, что нужно, но она написана на Python.Я решил поэкспериментировать с внедрением кода Python в C ++, используя Boost.Библиотеку Python.
Код на C ++ выглядит примерно так:
#include <string>
#include <iostream>
#include <boost/python.hpp>
using namespace boost::python;
int main(int, char **)
{
Py_Initialize();
try
{
object module((handle<>(borrowed(PyImport_AddModule("__main__")))));
object name_space = module.attr("__dict__");
object ignored = exec("from myModule import MyFunc\n"
"MyFunc(\"some_arg\")\n",
name_space);
std::string res = extract<std::string>(name_space["result"]);
}
catch (error_already_set)
{
PyErr_Print();
}
Py_Finalize();
return 0;
}
(Очень) упрощенная версия кода Python выглядит следующим образом:
import thirdparty
def MyFunc(some_arg):
result = thirdparty.go()
print result
Теперь проблема заключается в следующем:'myFunc' выполняется нормально, я вижу вывод 'result'.Чего я не могу сделать, так это прочитать "результат" обратно из кода на C ++.Команда extract никогда не находит "результат" ни в одном пространстве имен.Я попытался определить "результат" как глобальный, я даже попытался вернуть кортеж, но я не могу заставить его работать.
Решение
Прежде всего, измените свою функцию на return
значение. print
это усложнит ситуацию, так как вы хотите вернуть значение. Предположим, ваш MyModule.py
выглядит так:
import thirdparty
def MyFunc(some_arg):
result = thirdparty.go()
return result
Теперь, чтобы сделать то, что вы хотите, вы должны выйти за рамки простого встраивания, как говорится в документации . . Вот полный код для запуска вашей функции:
#include <Python.h>
int
main(int argc, char *argv[])
{
PyObject *pName, *pModule, *pFunc;
PyObject *pArgs, *pArg, *pResult;
int i;
Py_Initialize();
pName = PyString_FromString("MyModule.py");
/* Error checking of pName left out as exercise */
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule != NULL) {
pFunc = PyObject_GetAttrString(pModule, "MyFunc");
/* pFunc is a new reference */
if (pFunc) {
pArgs = PyTuple_New(0);
pArg = PyString_FromString("some parameter")
/* pArg reference stolen here: */
PyTuple_SetItem(pArgs, 0, pArg);
pResult = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pResult != NULL) {
printf("Result of call: %s\n", PyString_AsString(pResult));
Py_DECREF(pResult);
}
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");
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);
}
else {
PyErr_Print();
fprintf(stderr, "Failed to load module");
return 1;
}
Py_Finalize();
return 0;
}
Другие советы
Основываясь на ответах ΤΖΩΤΖΙΟΥ, Джоша и Носкло, я, наконец, заставил это работать, используя boost.python:
Питон:
import thirdparty
def MyFunc(some_arg):
result = thirdparty.go()
return result
C++:
#include <string>
#include <iostream>
#include <boost/python.hpp>
using namespace boost::python;
int main(int, char **)
{
Py_Initialize();
try
{
object module = import("__main__");
object name_space = module.attr("__dict__");
exec_file("MyModule.py", name_space, name_space);
object MyFunc = name_space["MyFunc"];
object result = MyFunc("some_args");
// result is a dictionary
std::string val = extract<std::string>(result["val"]);
}
catch (error_already_set)
{
PyErr_Print();
}
Py_Finalize();
return 0;
}
Некоторые важные моменты:
- Я изменил 'exec' на 'exec_file' из соображений удобства, это также работает с простым 'exec'.
- Главная причина, по которой это не удалось, заключается в том, что я не передавал "локальный" name_sapce в 'exec' или 'exec_file' - это сейчас исправлена путем передачи name_space дважды.
- Если функция python возвращает строки в юникоде, они не являются конвертируемыми в 'std::string', поэтому мне пришлось добавить суффикс ко всем строкам python с помощью '.encode('ASCII', 'игнорировать')'.
Я думаю, что вам нужно либо PyObject_CallObject(<py function>, <args>)
, который возвращает возвращаемое значение функции, которую вы вызываете как PyObject, либо PyRun_String(<expression>, Py_eval_input, <globals>, <locals>)
, которая оценивает одно выражение и возвращает его результат.
Вы должны иметь возможность вернуть результат из MyFunc, который затем окажется в переменной, которую вы в данный момент вызываете " ignored " ;. Это устраняет необходимость доступа к нему любым другим способом.