Boost Python 暴露 C++ 类,构造函数采用 std::list

Posted

技术标签:

【中文标题】Boost Python 暴露 C++ 类,构造函数采用 std::list【英文标题】:Boost Python Exposing C++ class with constructor taking a std::list 【发布时间】:2019-05-24 10:26:19 【问题描述】:

我有一个如下所示的类,

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

任何人都可以建议如何去做吗?

【问题讨论】:

我认为您希望能够通过将 Python 列表传递给它来在 Python 中构造这个对象? 此外,我是否正确假设您希望 MyClass 在内部仍使用 std::list(即包装应该是非侵入性的)? @DanMašek 这也是我的想法,我开始查看boost::python::extract 等,但我主要发现 boost 的 python 列表和标准 C++ 容器之间的手动传输,我希望它接受迭代器等。如果您对如何改进我的答案有任何提示,请发表评论,我会尽力而为。 @TedLyngmo 我有two possible approaches,但是一旦 OP 确认我的假设,我需要对其进行更多润色并写下解释。更简单的(如果我们只关心构造函数)是提供一个自定义的独立函数,它转换列表并在之后构造MyClass(并将其绑定为__init__)。其次,更复杂但更灵活的是为字符串列表提供到/从 python 转换器。 @DanMašek 不错! +1 来自我!我唯一感到失望的是这两个主要贡献社区缺乏集成支持。 Boost 在这方面有点令人失望——在某些情况下是 Forefront,但不得不做你为无缝集成所做的出色工作让我觉得他们已经把简化事情的艺术抛在了脑后。你做得很好! Danny:您现在可能想将已接受的答案转移给 Dans。它更彻底,我很乐意删除我的,以免以后误入歧途。 【参考方案1】:

想到了两种可能的方法。

假设我们试图以非侵入方式公开以下 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&lt;std::string&gt; 对象,反之亦然。

从 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: How do I provide a custom constructor wrapper function? 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/exposing.html#tutorial.exposing.constructors Feeding a Python list into a function taking in a vector with 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

【讨论】:

感谢您非常全面的回答!【参考方案2】:

这是我第一次尝试,但事实证明 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']

【讨论】:

哦,等等,对不起。你试过用python调用它吗? 类似于python中的MyClass(['abc', 'def']) 啊,我得到的错误是尝试从 python 调用它时。 我不使用 bjam,我主要使用 cmake,因为我在 CLion 环境中 如果尝试代码,您将收到我上面提到的错误消息。

以上是关于Boost Python 暴露 C++ 类,构造函数采用 std::list的主要内容,如果未能解决你的问题,请参考以下文章

在 boost python 中暴露 C++ 接口

Boost.Python - 暴露一个类

使用 boost::python 从 c++ 函数访问大表

boost-python:如何提供自定义构造函数包装函数?

将 C++ 暴露给来自 BoostPython 的 Python 错误

Boost.Python 列出所有暴露的类和属性