Запустить Python класс из кода C++

213
17 февраля 2018, 22:57

Добрый день.

Допустим что объявлен простой класс на языке python в файле example.py.

example.py

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def print_position(self):
        return "X: {0} Y: {1}\n".format(self.x, self.y)

Как вызвать метод print_position из кода написанного на C++?

main.cpp

#include<python2.7/Python.h>
#include <boost/python.hpp>
int main()
{
     Py_Initialize();
     ... CODE HERE
     Py_Finalize();
}

Смотрел в сторону Cyhton и boost.python но точно разобраться не получилось. Ссылки в которых что-то нашел:

  • Первая ссылка
  • Вторая ссылка
Answer 1

Отсюда (с небольшими правками под Python 3.5 и дополнительными проверками)

#include <Python.h>
int main(int argc, char *argv[]) {
    PyObject *pName, *pModule, *pDict, *pClass, *pInstance, *pValue;
    int i, arg[8];
    if (argc < 4) {
        fprintf(stderr, "Usage: call module_name class_name method_name [arguments]\n");
        return 1;
    }
    Py_Initialize();
    // Преобразуем первый аргумент в unicode-строку python    
    pName = PyUnicode_DecodeFSDefault(argv[1]);
    // Импортируем модуль
    // import module_name
    pModule = PyImport_Import(pName);
    Py_DECREF(pName);   // Освобождаем ссылку на строку с именем модуля
    if (pModule != NULL) {
        // Получаем пространство имён (__dict__) модуля
        pDict = PyModule_GetDict(pModule);
        // Получаем класс class_name
        pClass = PyDict_GetItemString(pDict, argv[2]);
        // Проверяем, что полученный class_name можно вызвать
        if (pClass && PyCallable_Check(pClass)) {
            // Получаем объект
            // obj = class_name()
            pInstance = PyObject_CallObject(pClass, NULL); 
            if(pInstance != NULL) {
                // Подготавливаем параметры
                if(argc > 4) {
                    for (i = 0; i < argc - 4; i++) {
                        arg[i] = atoi(argv[i + 4]);
                    }
                    // Вызываем метод с двумя параметрами
                    // value = obj.multiply2(3, 2)
                    pValue = PyObject_CallMethod(pInstance, argv[3], "(ii)", arg[0], arg[1]);
                } 
                else {
                    // Вызываем метод без параметров
                    // value = obj.multiply()
                    pValue = PyObject_CallMethod(pInstance, argv[3], NULL);
                }
                if (pValue != NULL) {
                    printf("Return of call : %d\n", PyLong_AsLong(pValue));
                    // Освобождаем ссылку на выделенную в памяти
                    // переменную для возвращённого из метода результата
                    Py_DECREF(pValue);
                }
                else {
                    PyErr_Print();
                }
                Py_DECREF(pInstance); // Освобождаем ссылку на объект
            }
            else {
                PyErr_Print();
            }
            Py_DECREF(pClass); // Освобождаем ссылку на класс
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find class \"%s\"\n", argv[2]);        
        }
        Py_DECREF(pDict); // Освобождаем ссылку на пространство имён
        Py_DECREF(pModule); // Освобождаем ссылку на модуль
    }
    else {
        PyErr_Print();
    }
    Py_Finalize();
    return 0;
}

Как заметил @jfs, это только пример, код сильно упрощён для ясности. В боевом проекте на те же операции будет раза в два больше кода. Или в четыре, если понадобится многопоточность.

Answer 2

При ответе на вопрос очень помогли ответы выше и ответы по ссылке.

Структура проекта следующая: в одной папке лежат файлы main.cpp и example.py.

Файл example.py

class point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def print_position(self):
        return "X: {0} Y: {1}\n".format(self.x, self.y)

Файл main.cpp:

#include <iostream>
#include <cstring>
#include <python2.7/Python.h>
//#include <boost/python.hpp>
int main(int argc, char* argv[])
{
    Py_Initialize();
    // Для импорта созданного мною модуля необходимо передать 
    // его имя как аргумент командной строки (у меня example.py)
    PySys_SetArgv(argc, argv);
    PyObject* module_name = PyString_FromString("example");
    // Загрузка модуля (example.py)
    PyObject* module = PyImport_Import(module_name);
    if (module == nullptr)
    {
        PyErr_Print();
        std::cerr << "Failed to import module\n";
        return 1;
    }
    // Загрузка пространств имен модуля
    PyObject* dict = PyModule_GetDict(module);
    if (dict == nullptr)
    {
        PyErr_Print();
        std::cerr << "Failed to import __dict__\n";
        return 1;
    }
    // Загрузка класса из example.py
    std::string py_class_name = "point";
    PyObject* py_class = PyDict_GetItemString(dict, py_class_name.c_str());
    if(py_class == nullptr)
    {
        PyErr_Print();
        std::cerr << "Failed import class " << py_class_name << std::endl;
        return 1;
    }
    // Поскольку конструктор принимает пару аргументов
    // То необходимо создать пару аргументов
    PyObject* py_arg_tuple = PyTuple_New(2);
    PyTuple_SetItem(py_arg_tuple, 0, PyInt_FromLong(5));
    PyTuple_SetItem(py_arg_tuple, 1, PyInt_FromLong(10));
    // Создание объекта класса point
    PyObject* obj;
    if (PyCallable_Check(py_class))
        obj = PyObject_CallObject(py_class, py_arg_tuple);
    else
        std::cout << "Cannot instantiate the Python class" << std::endl;
    // Вызываем метод print_position класса point
    PyObject* val = PyObject_CallMethod(obj, "print_position", NULL);
    if (!val)
        PyErr_Print();
    // конвертируем результат в std::string и печатаем
    std::string s (PyString_AsString(val));
    std::cout << s;
    Py_Finalize();
    return 0;
}

Для запуска под ubuntu в консоли необходимо ввести команды:

$ g++ main.cpp -std=c++11 -lpython2.7
$ ./a.out example

P.S. аргумет командной строки совпадает с названием импортируемого и написанного мною pyhton модуля.

READ ALSO
Как объявить объект System.String C++

Как объявить объект System.String C++

Не понимаю, как объявить и использовать объекты класса System StringНа msdm информации как таковой нет, примеров в интернете тоже не нашел

204
Узнать время между датами

Узнать время между датами

Использую такую структуру и функцию для получения текущего системного времени

213
Почему не работают слоты в загруженной форме через QUiLoader?

Почему не работают слоты в загруженной форме через QUiLoader?

Не хотят работать слоты в загруженой форме через QUiLoaderВот конструктор базового класса:

206