Передача экземпляра класса Python в функцию C ++

У меня есть собственная библиотека, написанная на C ++, которую я сейчас работаю над расширением до Python. Я начал эту задачу, имея в виду Boost.Python, но я открыт для альтернатив.

В настоящее время у меня есть функция C ++, которая должна принимать экземпляр класса Python, а затем использовать методы этого объекта для выполнения определенных задач. Идея состоит в том, чтобы пользователю Python никогда не приходилось иметь дело с C ++. Ожидается, что они создадут этот объект Python из шаблона / примера класса Python, который я предоставлю, с предустановленными именами методов, которые, как я могу предположить, присутствуют в моей библиотеке C ++.

Интерфейс, доступный пользователю Python, выглядит так:

class Foo(object):

    def __init__(self, args):
        """create an instance of this class with instance-specific attributes"""

    def Bar1(self, a, b, c):
        """do something with the given integers a, b and c"""
        pass

    def Bar2(self, a, b, c):
        """do something else with the given integers a, b and c"""
        pass

import mylib

cheese = mylib.Wine()
cheese.do_something(Foo)

В C ++ соответствующий код выглядит так:

#include <boost/python.h>
#include <Python.h>

class Wine {

public:  

    Wine() {};

    ~Wine() {};

    static void do_something(boost::python::object *Foo) {

        int a = 1;
        int b = 2;
        int c = 3;

        Foo->attr("Bar1")(a, b, c);
        Foo->attr("Bar2")(a, b, c);
    };
};

BOOST_PYTHON_MODULE(mylib)
{
    using namespace boost::python;
    class_<Wine>("Wine")
        .def("do_something", &Wine::do_something);
};

Я успешно скомпилировал этот код и убедился, что класс C ++ под названием Wine действительно доступен для Python, и я могу получить доступ к его функциям-членам. Если я напишу функцию-член с именем «greet ()», которая возвращает только «Привет, мир!», Она будет работать отлично.

Я должен подчеркнуть здесь важность передачи экземпляра Foo. У меня нет возможности просто импортировать модуль Foo в код C ++ и создать экземпляр Foo в C ++. Объект, который я хочу получить от пользователя Python, имеет атрибуты, которые мне нужно использовать, которые относятся к экземпляру, а не к самому классу.

Проблема в том, что я не могу понять, как передать экземпляр Python в do_something, чтобы он отображался в C ++ как вызываемый boost :: python :: object. Приведенный выше код возвращает следующую ошибку несоответствия сигнатуры C ++:

Boost.Python.ArgumentError: Python argument types in
    Wine.do_something(Wine, Foo)
did not match C++ signature:
    do_something(boost::python::api::object*)

Два дня поиска ответов в Интернете ни к чему не привели. Кажется, существует огромное количество информации о том, как передавать классы C ++ в Python, но мне не удалось найти информацию о том, как сделать это в обратном направлении. Был бы очень признателен за руководство здесь.

Спасибо!


person Alp Dener    schedule 03.05.2014    source источник


Ответы (2)


В исходном коде есть две ошибки:

  • Boost.Python пытается передать два аргумента (self и экземпляр Foo) статической функции Wine::do_something() C ++, которая принимает только один аргумент. Чтобы решить эту проблему, при раскрытии класса Wine функция-член Python Wine.do_something() должна быть установлена ​​как статическая с помощью _ 6_ функция-член. Когда он представлен как статический метод, Boost.Python больше не будет передавать аргумент экземпляра self.
  • В отличие от Python / C API, где указатели часто используются в качестве дескрипторов объектов (PyObject*), Boost.Python предоставляет нотацию более высокого уровня _ 9_ класс, который часто передается по значению или ссылке. Внутри этот класс взаимодействует с _10 _, который выполняет интеллектуальное управление указателями для PyObject.

Вот полное расширение Python, основанное на исходном коде:

#include <boost/python.hpp>

class Wine
{
public:  

  static void do_something(boost::python::object object)
  {
    int a = 1;
    int b = 2;
    int c = 3;

    object.attr("Bar1")(a, b, c);
    object.attr("Bar2")(a, b, c);
  };
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<Wine>("Wine")
    .def("do_something", &Wine::do_something)
      .staticmethod("do_something")
    ;
};

Интерактивное использование:

>>> class Foo(object):
...     def Bar1(self, a, b, c):
...         print "Bar1", locals()
...     def Bar2(self, a, b, c):
...         print "Bar2", locals()
... 
>>> import example
>>> cheese = example.Wine()
>>> cheese.do_something(Foo())
Bar1 {'a': 1, 'c': 3, 'b': 2, 'self': <__main__.Foo object at 0xb6b0f2ac>}
Bar2 {'a': 1, 'c': 3, 'b': 2, 'self': <__main__.Foo object at 0xb6b0f2ac>}
person Tanner Sansbury    schedule 03.05.2014
comment
Это невероятно полезно. Я сообщу о его успехе, когда вернусь к своему компьютеру, но это определенно имеет большой смысл. Хотя есть один вопрос. Если я правильно понимаю, вы подразумеваете, что класс объекта boost уже обрабатывает интеллектуальный указатель на pyobject, и поэтому принятие объекта вместо объекта * не создаст копию Foo. Если так, то это хорошие новости, потому что мы действительно не хотим здесь клонировать Foo. На практике он задуман как очень большой объект. - person Alp Dener; 04.05.2014
comment
@AlpDener Рад, что это помогло. Копия Foo не будет сделана. Boost.Python поддерживает семантику передачи аргументов Python, где ссылки на объекты передаются по значению. Для получения дополнительной информации о времени жизни и потоковой передаче (обратите внимание, что в другом вашем комментарии упоминается параллелизм), рассмотрите возможность чтения этого ответа. - person Tanner Sansbury; 04.05.2014
comment
У меня была возможность реализовать это в моем коде, и я рад сообщить, что он работает безупречно. Также добавил в закладки ваш ответ о многопоточности, на случай, если мы столкнемся с этим в будущем. Очень признателен! - person Alp Dener; 04.05.2014

Чтобы предоставить метод, который принимает объект Python в качестве аргумента, вы должны использовать boost::python::object, а не boost::python::object *

void do_something(boost::python::object Foo)

Чтобы предоставить статический метод, откройте его как обычную функцию: def("do_something", Wine::do_something);

import mylib

# method
cheese = mylib.Wine()
foo = Foo()
cheese.do_something(foo)

#static method
foo = Foo()
mylib.do_something(foo)
person Slava Zhuyko    schedule 03.05.2014
comment
Это делает копию Foo на C ++? Это очень нежелательно. Объект Foo должен быть довольно большим (и сильно распараллеливаться). Мы хотим, чтобы функция C ++ имела только ссылку на этот объект Python. - person Alp Dener; 04.05.2014