当我们编写 C++ 库的封装器通常涉及使用一种跨语言的接口技术,比如使用C接口或者使用特定的跨语言库,比如SWIG(Simplified Wrapper and Interface Generator)或者Pybind11。这里我将简要介绍如何使用Pybind11来封装一个C++库,以便在Python中使用。
1、问题背景
在编写 C++ 库的封装器时,需要将 C++ 中的 list 容器转换为 Python 中的 list。由于 C++ 库不能被修改,因此希望避免使用 vector 来替代 list。
为了更好地理解这种情况,使用 list 作为代理来注册从 C++ 到 Python 的转换(只读)。当前的实现可以编译,Python 可以正常导入,并且可以创建对象,但是在调用数据成员时会出错。
Python 控制台输出:
In [1]: import my_list
In [2]: x = my_list.Bob()
In [3]: x.foos
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-2f015d13a87d> in <module>()
----> 1 x.foos
TypeError: No Python class registered for C++ class std::list<int, std::allocator<int> >
C++ 文件:
#include <list>
#include <boost/python.hpp>
#include <boost/foreach.hpp>
#ifndef FOREACH
#define FOREACH BOOST_FOREACH
#endif
using namespace std;
using namespace boost::python;
template<typename T>
struct list_to_list
{
static PyObject* convert(const std::list<T>& src)
{
boost::python::list result;
FOREACH (const T& val, src)
{
result.append(val);
}
return incref(result.ptr());
}
};
struct Bob
{
std::list<int> foos;
};
BOOST_PYTHON_MODULE(my_list)
{
using namespace boost::python;
to_python_converter<std::list<int>, list_to_list<int> >();
class_<Bob>("Bob")
.def_readonly("foos", &Bob::foos)
;
}
2、解决方案
如常见问题解答中所述,通过 def_readonly()
、def_readwrite()
和 add_property()
暴露的属性,使用默认策略不会使用自定义转换器。要解决此问题,请用 add_property()
替换 def_readonly()
和 def_readwrite()
,提供一个 boost::python::return_value_policy
,其类型为 boost::python::return_by_value
。
在原始代码中,它应该是:
BOOST_PYTHON_MODULE(my_list)
{
using namespace boost::python;
to_python_converter<std::list<int>, list_to_list<int> >();
class_<Bob>("Bob")
.add_property("foos", make_getter(&Bob_foos,
return_value_policy<return_by_value>()))
;
}
完整的示例如下:
#include <list>
#include <boost/assign/list_of.hpp>
#include <boost/foreach.hpp>
#include <boost/python.hpp>
namespace python = boost::python;
/// @brief Type to convert from an iterable to a Python list.
template <typename T>
struct list_to_list
{
static PyObject* convert(const std::list<T>& container)
{
python::list result;
BOOST_FOREACH(const T& value, container)
result.append(value);
return python::incref(result.ptr());
}
};
/// @brief mockup type.
struct Spam
{
Spam()
{
foos = boost::assign::list_of(1)(2)(3)(5);
}
std::list<int> foos;
};
BOOST_PYTHON_MODULE(example)
{
// Enable std::list<int> to Python list conversion.
python::to_python_converter<std::list<int>, list_to_list<int> >();
python::class_<Spam>("Spam")
.add_property("foo", python::make_getter(&Spam::foos,
python::return_value_policy<python::return_by_value>()))
;
}
相应的用法:
>>> import example
>>> spam = example.Spam()
>>> spam.foo
[1, 2, 3, 5]
上面这个就是一个简单的例子,展示了如何使用Pybind11来封装一个C++库。具体的细节可能会因为我们的C++库的复杂性而有所不同,但这应该可以帮助你入门。如果有更多的问题可以留言讨论。