Повышение Python Предоставление класса C ++ с конструктором, принимающим std :: list - PullRequest
2 голосов
/ 24 мая 2019

У меня есть класс, который выглядит следующим образом,

class MyClass
{
    MyClass( std::list<std::string> );

};

Я попытался экспортировать его в python, используя

class_<MyClass, boost::noncopyable >("MyClass", init<std::list<std::string>>());

Но я получил ошибку несоответствия подписи,

did not match C++ signature:
__init__(_object*, std::__cxx11::list<std::__cxx11::basic_string<char,
std::char_traits<char>, std::allocator<char> > )

Может кто-нибудь посоветовать, как это сделать?

Ответы [ 2 ]

1 голос
/ 25 мая 2019

На ум приходят два возможных подхода.

Давайте предположим, что мы пытаемся незаметно представить следующий класс C ++:

class MyClass
{
public:
    MyClass(std::list<std::string> messages)
        : msgs(std::move(messages))
    {
    }

    void dump() const
    {
        std::cout << "Messages:\n";
        for (auto const& msg : msgs) {
            std::cout << msg << "\n";
        }
    }

    // NB: This would be better returned as const ref
    //     but I have issues exposing that to Python
    //     that I have yet to solve
    std::list<std::string> messages() const
    {
        return msgs;
    }

private:
    std::list<std::string> msgs;
};

Если единственное место, где нам нужно иметь дело с std::list, это конструктор, то самый простой подход - написать небольшую "фабричную" функцию, которая будет

  • Взять список Python в качестве входных данных.
  • Создайте std::list и заполните его значениями из списка Python.
  • Создание экземпляра MyClass с std::list.
  • Вернуть этот экземпляр MyClass.

Мы будем использовать shared_ptr для управления памятью. Чтобы легко инициализировать std::list, мы можем воспользоваться boost::python::stl_input_iterator.

boost::shared_ptr<MyClass> create_MyClass(bp::list const& l)
{
    std::list<std::string> messages{ bp::stl_input_iterator<std::string>(l)
        , bp::stl_input_iterator<std::string>() };
    return boost::make_shared<MyClass>(messages);
}

Как только мы получим эту функцию, мы представим ее вместо исходного конструктора MyClass. Для этого сначала нужно отключить любые привязки конструктора по умолчанию, поэтому мы используем boost::python::no_init. В python конструкторы - это просто функции с именем __init__. Наконец, нам нужно использовать явно недокументированную функцию boost::python::make_constructor для создания объекта функции appriate.

BOOST_PYTHON_MODULE(so07)
{
    bp::class_<MyClass, boost::noncopyable, boost::shared_ptr<MyClass>>("MyClass", bp::no_init)
        .def("__init__", bp::make_constructor(create_MyClass))
        .def("dump", &MyClass::dump)
        ;
}

Стенограмма:

>>> import so07
>>> test = so07.MyClass(['a','b','c'])
>>> test.dump()
Messages:
a
b
c

Если мы хотим использовать std::list в других контекстах, то написание отдельных функций-оберток для работы с переводом быстро выйдет из-под контроля. Чтобы избежать этого, мы можем зарегистрировать пользовательские конвертеры, которые позволят Boost.Python автоматически конвертировать списки Python, содержащие строки, в std::list<std::string> объекты и наоборот.

Переход от C ++ к python довольно прост - просто создайте boost::python::list и затем добавьте все элементы из списка C ++. Мы можем зарегистрировать этот конвертер используя boost::python::to_python_converter.

struct std_list_to_python
{
    static PyObject* convert(std::list<std::string> const& l)
    {
        bp::list result;
        for (auto const& value : l) {
            result.append(value);
        }
        return bp::incref(result.ptr());
    }
};

Переход с Python на C ++ является двухэтапным процессом. Прежде всего, нам нужна функция, чтобы определить, является ли ввод действительным кандидатом для преобразования. В этом случае объект должен быть списком Python, а каждый его элемент должен быть строкой Python. Второй этап состоит в создании на месте std::list и последующем заполнении его элементами из списка Python. Мы регистрируем этот конвертер, используя boost::python::converter::registry::push_back.

struct pylist_converter
{
    static void* convertible(PyObject* object)
    {
        if (!PyList_Check(object)) {
            return nullptr;
        }

        int sz = PySequence_Size(object);
        for (int i = 0; i < sz; ++i) {
            if (!PyString_Check(PyList_GetItem(object, i))) {
                return nullptr;
            }
        }

        return object;
    }

    static void construct(PyObject* object, bp::converter::rvalue_from_python_stage1_data* data)
    {
        typedef bp::converter::rvalue_from_python_storage<std::list<std::string>> storage_type;
        void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

        data->convertible = new (storage) std::list<std::string>();

        std::list<std::string>* l = (std::list<std::string>*)(storage);

        int sz = PySequence_Size(object);
        for (int i = 0; i < sz; ++i) {
            l->push_back(bp::extract<std::string>(PyList_GetItem(object, i)));
        }
    }
};

Наш модуль будет выглядеть следующим образом:

BOOST_PYTHON_MODULE(so07)
{
    bp::to_python_converter<std::list<std::string>, std_list_to_python>();

    bp::converter::registry::push_back(&pylist_converter::convertible
        , &pylist_converter::construct
        , bp::type_id<std::list<std::string>>());

    bp::class_<MyClass, boost::noncopyable>("MyClass", bp::init<std::list<std::string>>())
        .def("dump", &MyClass::dump)
        .def("messages", &MyClass::messages)
        ;
}

Стенограмма:

>>> import so07
>>> test = so07.MyClass(['a','b','c'])
>>> test.dump()
Messages:
a
b
c
>>> test.messages()
['a', 'b', 'c']

Ссылка:


Полный код:

#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

#include <list>
#include <iostream>

namespace bp = boost::python;

class MyClass
{
public:
    MyClass(std::list<std::string> messages)
        : msgs(std::move(messages))
    {
    }

    void dump() const
    {
        std::cout << "Messages:\n";
        for (auto const& msg : msgs) {
            std::cout << msg << "\n";
        }
    }

    std::list<std::string> messages() const
    {
        return msgs;
    }

private:
    std::list<std::string> msgs;
};

#if 1

boost::shared_ptr<MyClass> create_MyClass(bp::list const& l)
{
    std::list<std::string> messages{ bp::stl_input_iterator<std::string>(l)
        , bp::stl_input_iterator<std::string>() };
    return boost::make_shared<MyClass>(messages);
}

BOOST_PYTHON_MODULE(so07)
{
    bp::class_<MyClass, boost::noncopyable, boost::shared_ptr<MyClass>>("MyClass", bp::no_init)
        .def("__init__", bp::make_constructor(create_MyClass))
        .def("dump", &MyClass::dump)
        ;
}

#else

struct std_list_to_python
{
    static PyObject* convert(std::list<std::string> const& l)
    {
        bp::list result;
        for (auto const& value : l) {
            result.append(value);
        }
        return bp::incref(result.ptr());
    }
};


struct pylist_converter
{
    static void* convertible(PyObject* object)
    {
        if (!PyList_Check(object)) {
            return nullptr;
        }

        int sz = PySequence_Size(object);
        for (int i = 0; i < sz; ++i) {
            if (!PyString_Check(PyList_GetItem(object, i))) {
                return nullptr;
            }
        }

        return object;
    }

    static void construct(PyObject* object, bp::converter::rvalue_from_python_stage1_data* data)
    {
        typedef bp::converter::rvalue_from_python_storage<std::list<std::string>> storage_type;
        void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

        data->convertible = new (storage) std::list<std::string>();

        std::list<std::string>* l = (std::list<std::string>*)(storage);

        int sz = PySequence_Size(object);
        for (int i = 0; i < sz; ++i) {
            l->push_back(bp::extract<std::string>(PyList_GetItem(object, i)));
        }
    }
};


BOOST_PYTHON_MODULE(so07)
{
    bp::to_python_converter<std::list<std::string>, std_list_to_python>();

    bp::converter::registry::push_back(&pylist_converter::convertible
        , &pylist_converter::construct
        , bp::type_id<std::list<std::string>>());

    bp::class_<MyClass, boost::noncopyable>("MyClass", bp::init<std::list<std::string>>())
        .def("dump", &MyClass::dump)
        .def("messages", &MyClass::messages)
        ;
}

#endif
0 голосов
/ 24 мая 2019

Это мой первый опыт, но оказалось, что у boost есть свой собственный тип списка питонов

Это действительно работает:

#include <boost/python.hpp>
#include <boost/python/list.hpp>
#include <boost/python/extract.hpp>
#include <string>

using namespace boost::python;

struct MyClass {
    MyClass(boost::python::list messages) : msgs(messages) {}
    void set(boost::python::list messages) { msgs = messages; }
    boost::python::list get() { return msgs; }

    boost::python::list msgs;
};

BOOST_PYTHON_MODULE(my_first) {
    class_<MyClass, boost::noncopyable>("MyClass", init<boost::python::list>())
        .def("get", &MyClass::get)
        .def("set", &MyClass::set);
}

Сессия:

PYTHONPATH="." python3
>>> from my_first import MyClass
>>> a = MyClass(['a', 'b'])
>>> b = a.get()
>>> print(b)
['a', 'b']

...