python - Boost Python Exposing C++ class with constructor taking a std::list

标签 python c++ boost-python

我有一个如下所示的类,

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> > )

有人可以建议如何去做吗?

最佳答案

我想到了两种可能的方法。

假设我们正在尝试以非侵入方式公开以下 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 列表中的值填充它。
  • 实例化MyClassstd::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构造函数。为此,我们首先需要禁用任何默认构造函数绑定(bind),因此我们使用 boost::python::no_init .在 python 中,构造函数只是名为 __init__ 的函数。 .最后,我们需要使用明显未记录的函数 boost::python::make_constructor 创建一个适当的函数对象。

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

关于python - Boost Python Exposing C++ class with constructor taking a std::list,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56290774/

相关文章:

python - 如何获取新定义的变量

c++ - 为什么我得到 NaN 作为计算结果? (拉格朗日标准形式的分段线性插值)

c++ - 将其建模为 BFS 背后的直觉

c++ - boost Python 可移植性问题

boost-python - 如何在主函数中使用 boost::python::dict 或元组?

python - 从扫描文档中的图表中提取数据

python - 为什么 tf.gradient 和 tf.where 返回 None ?

python - 使用 pathlib 的 rglob 处理缺少权限/组的情况

c++ - 当这个成员是指针时,如何保证模板类成员被正确释放?

python - Qt 和 PyQt 混合应用