Boost Python 使用构造函数公开 C++ class std::list
Boost Python Exposing C++ class with constructor taking a std::list
我有一个 class,如下所示,
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> > )
任何人都可以建议如何去做吗?
这是我第一次尝试,但事实证明 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']
我想到了两种可能的方法。
假设我们正在尝试公开以下 C++ class、non-intrusively:
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
的地方是构造函数,那么最简单的方法是编写一个小的 "factory" 函数,它将
- 将 Python 列表作为输入。
- 创建一个
std::list
并用 Python 列表中的值填充它。
- 用
std::list
实例化 MyClass
。
- Return
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++ 是一个 two-step 的过程。首先,我们需要一个函数来确定输入是否是转换的有效候选者。在这种情况下,对象需要是一个 Python 列表,并且它的每个元素都需要是一个 Python 字符串。第二步包括 in-place 构建 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
我有一个 class,如下所示,
class MyClass
{
MyClass( std::list<std::string> );
};
我尝试使用
将其导出到 pythonclass_<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> > )
任何人都可以建议如何去做吗?
这是我第一次尝试,但事实证明 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']
我想到了两种可能的方法。
假设我们正在尝试公开以下 C++ class、non-intrusively:
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
的地方是构造函数,那么最简单的方法是编写一个小的 "factory" 函数,它将
- 将 Python 列表作为输入。
- 创建一个
std::list
并用 Python 列表中的值填充它。 - 用
std::list
实例化MyClass
。 - Return
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++ 是一个 two-step 的过程。首先,我们需要一个函数来确定输入是否是转换的有效候选者。在这种情况下,对象需要是一个 Python 列表,并且它的每个元素都需要是一个 Python 字符串。第二步包括 in-place 构建 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