Передача объекта C++ в Python
Этот вопрос связан с тем, как передать объект C++ на функцию python, которая вызывается в встроенном интерпретаторе Python (C++).
Следующий класс C++ (MyClass.h) предназначен для тестирования:
#ifndef MyClassH
#define MyClassH
#include <string>
using std::string;
class MyClass
{
public:
MyClass(const string& lbl): label(lbl) {}
~MyClass(){}
string getLabel() {return label;}
private:
string label;
};
#endif
Модуль python, предоставляющий класс C++, может быть сгенерирован следующим файлом интерфейса Swig:
%module passmetopython
%{ #include "MyClass.h" %}
%include "std_string.i"
//Expose to Python
%include "MyClass.h"
Ниже приведен скрипт Python с использованием модуля python
import passmetopython as pmtp
def execute(obj):
#This function is to be called from C/C++, with a
#MyClass object as an argument
print ("Entering execute function")
lbl = obj.getLabel();
print ("Printing from within python execute function. Object label is: " + lbl)
return True
def main():
c = pmtp.MyClass("Test 1")
retValue = execute(c)
print("Return value: " + str(retValue))
#Test function from within python
if __name__ == '__main__':
main()
Этот вопрос касается того, как заставить функцию execute() python работать при вызове из C++ с объектом C++ в качестве аргумента.
Следующая программа C++ была написана для проверки функций (минимальный объем проверки ошибок):
#include "Python.h"
#include <iostream>
#include <sstream>
#include "MyClass.h"
using namespace std;
int main()
{
MyClass obj("In C++");
cout << "Object label: \"" << obj.getLabel() << "\"" << endl;
//Setup the Python interpreter and eventually call the execute function in the
//demo python script
Py_Initialize();
//Load python Demo script, "passmetopythonDemo.py"
string PyModule("passmetopythonDemo");
PyObject* pm = PyUnicode_DecodeFSDefault(PyModule.c_str());
PyRun_SimpleString("import sys");
stringstream cmd;
cmd << "sys.path.append(\"" << "." << "\")";
PyRun_SimpleString(cmd.str().c_str());
PyObject* PyModuleP = PyImport_Import(pm);
Py_DECREF(pm);
//Now create PyObjects for the Python functions that we want to call
PyObject* pFunc = PyObject_GetAttrString(PyModuleP, "execute");
if(pFunc)
{
//Setup argument
PyObject* pArgs = PyTuple_New(1);
//Construct a PyObject* from long
PyObject* pObj(NULL);
/* My current attempt to create avalid argument to Python */
pObj = PyLong_FromLong((long) &obj);
PyTuple_SetItem(pArgs, 0, pObj);
/***** Calling python here *****/
cout<<endl<<"Calling function with an MyClass argument\n\n";
PyObject* res = PyObject_CallObject(pFunc, pArgs);
if(!res)
{
cerr << "Failed calling function..";
}
}
return 0;
}
При запуске вышеуказанного кода функция execute() python с объектом MyClass в качестве аргумента терпит неудачу и возвращает NULL. Тем не менее, функция Python вводится, так как я могу видеть вывод (Enter execute execute) в выводе консоли, указывая, что переданный объект не является действительно действительным объектом MyClass.
Существует множество примеров того, как передавать Python с C/C++ простые типы, такие как ints, double или string types. Но очень мало примеров, показывающих, как передать объект/указатель C/C++, что вызывает недоумение.
Вышеприведенный код с файлом CMake можно проверить из github: https://github.com/TotteKarlsson/miniprojects/tree/master/passMeToPython
Этот код не должен использовать какой-либо повышающий python или другой API. Cython звучит интересно, хотя, и если его можно использовать для упрощения на стороне C++, это может быть приемлемым.
Ответы
Ответ 1
Это частичный ответ на мой собственный вопрос. Я говорю частично, потому что я верю, что есть лучший способ.
Основываясь на этом сообщении http://swig.10945.n7.nabble.com/Pass-a-Swig-wrapped-C-class-to-embedded-Python-code-td8812.html Я сгенерировал заголовок времени выполнения swig, как описано здесь, раздел 15.4: http://www.swig.org/Doc2.0/Modules.html#Modules_external_run_time
Включая сгенерированный заголовок в коде C++ выше, разрешите писать следующий код:
PyObject* pObj = SWIG_NewPointerObj((void*)&obj, SWIG_TypeQuery("_p_MyClass"), 0 );
Этот код использует информацию из исходных файлов оболочки Swig python, а именно "swig" имя типа MyClass, то есть _p_MyClass.
С приведенным выше PyObject * в качестве аргумента функции PyObject_CallObject функция python execute() в приведенном выше коде выполняется отлично, а код Python с использованием сгенерированного модуля python имеет правильный доступ к внутренним данным объектов MyClass. Это замечательно.
Хотя приведенный выше код иллюстрирует, как передавать и извлекать данные между C++ и Python довольно простым способом, это, на мой взгляд, не идеальное.
Использование заголовочного файла swig в коде C++ на самом деле не так уж и красиво, и, кроме того, он требует, чтобы пользователь "вручную" просматривал сгенерированный код оболочки swig, чтобы найти код "_p_MyClass".
Должен быть лучший способ !? Возможно, что-то должно быть добавлено в файл интерфейса swig, чтобы это выглядело лучше?
Ответ 2
PyObject *pValue;
pValue = PyObject_CallMethod(pInstance, "add","(i)",x);
if (pValue)
Py_DECREF(pValue);
else
PyErr_Print();