Boost Python使用std :: list展示C 类

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

本文关键字:展示 list Boost 使用 std Python      更新时间:2023-10-16

我有一个看起来如下的类,

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列表中的值填充它。
  • std::list实例化MyClass
  • 返回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来创建批准功能对象。

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']

参考:

  • Boost-Python:我如何提供自定义构造函数功能?
  • https://wiki.python.org/moin/boost.python/howto#named_constructors_.2f_factories_.28as_python_initializers.29
  • https://www.boost.org/doc/libs/1_70_0/libs/python/doc/html/tutorial/tutorial/tutorial/exposing.html#tutorial.exposing.exposing.constructors.constructors.constructors
  • 将Python列表馈入使用Boost Python的向量吸收的功能

完成代码:

#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

这是我的第一个去,但是事实证明,boost有自己的python列表类型

这实际上确实有效:

#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']