将 Python 列表输入到使用 Boost Python 接收向量的函数中 [英] Feeding a Python list into a function taking in a vector with Boost Python
问题描述
我有一个带有签名的函数:
I've got a function with the signature:
function(std::vector<double> vector);
我已经公开了它,但它不包含 Python 列表.我查看了其他 SO 答案,其中大多数涉及更改函数以接收 boost::python::lists,但我不想更改函数.我想我可以使用 vector_indexing_suite 围绕这个函数编写一个简单的包装器,但是我有很多这种形式的函数,我不想为每个单独的函数编写一个包装器.有没有办法自动使 Python list->std::vector 映射发生?
And I've exposed it, but it doesn't take in Python lists. I've looked through the other SO answers, and most involve changing the function to take in boost::python::lists, but I don't want to change the function. I imagine I can use the vector_indexing_suite to write a simple wrapper around this function, but I have many functions of this form and would rather not write a wrapper for every single one. Is there a way to automatically make a Python list->std::vector mapping occur?
推荐答案
有一些解决方案可以在不修改原始函数的情况下完成此任务.
There are a few solutions to accomplish this without having to modify the original functions.
要使用少量样板代码和对 python 的透明度来完成此操作,请考虑注册一个 自定义转换器
.Boost.Python 在 C++ 和 Python 类型之间转换时使用已注册的转换器.一些转换器是在创建绑定时隐式创建的,例如在 class_
导出类型时.
To accomplish this with a small amount of boilerplate code and transparency to python, consider registering a custom converter
. Boost.Python uses registered converters when going between C++ and Python types. Some converters are implicitly created when creating bindings, such as when class_
exports a type.
以下完整示例使用 iterable_converter
类型,该类型允许从支持 python 可迭代协议.该示例启用以下转换:
The following complete example uses an iterable_converter
type that allows for the registration of conversion functions from a python type supporting the python iterable protocol. The example enable conversions for:
- 内置类型的集合:
std::vector
- 字符串的二维集合:
std::vector
- 用户类型集合:
std::list<foo>
#include <iostream>
#include <list>
#include <vector>
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
/// @brief Mockup model.
class foo {};
// Test functions demonstrating capabilities.
void test1(std::vector<double> values)
{
for (auto&& value: values)
std::cout << value << std::endl;
}
void test2(std::vector<std::vector<std::string> > values)
{
for (auto&& inner: values)
for (auto&& value: inner)
std::cout << value << std::endl;
}
void test3(std::list<foo> values)
{
std::cout << values.size() << std::endl;
}
/// @brief Type that allows for registration of conversions from
/// python iterable types.
struct iterable_converter
{
/// @note Registers converter from a python interable type to the
/// provided type.
template <typename Container>
iterable_converter&
from_python()
{
boost::python::converter::registry::push_back(
&iterable_converter::convertible,
&iterable_converter::construct<Container>,
boost::python::type_id<Container>());
// Support chaining.
return *this;
}
/// @brief Check if PyObject is iterable.
static void* convertible(PyObject* object)
{
return PyObject_GetIter(object) ? object : NULL;
}
/// @brief Convert iterable PyObject to C++ container type.
///
/// Container Concept requirements:
///
/// * Container::value_type is CopyConstructable.
/// * Container can be constructed and populated with two iterators.
/// I.e. Container(begin, end)
template <typename Container>
static void construct(
PyObject* object,
boost::python::converter::rvalue_from_python_stage1_data* data)
{
namespace python = boost::python;
// Object is a borrowed reference, so create a handle indicting it is
// borrowed for proper reference counting.
python::handle<> handle(python::borrowed(object));
// Obtain a handle to the memory block that the converter has allocated
// for the C++ type.
typedef python::converter::rvalue_from_python_storage<Container>
storage_type;
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
typedef python::stl_input_iterator<typename Container::value_type>
iterator;
// Allocate the C++ type into the converter's memory block, and assign
// its handle to the converter's convertible variable. The C++
// container is populated by passing the begin and end iterators of
// the python object to the container's constructor.
new (storage) Container(
iterator(python::object(handle)), // begin
iterator()); // end
data->convertible = storage;
}
};
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Register interable conversions.
iterable_converter()
// Build-in type.
.from_python<std::vector<double> >()
// Each dimension needs to be convertable.
.from_python<std::vector<std::string> >()
.from_python<std::vector<std::vector<std::string> > >()
// User type.
.from_python<std::list<foo> >()
;
python::class_<foo>("Foo");
python::def("test1", &test1);
python::def("test2", &test2);
python::def("test3", &test3);
}
互动使用:
>>> import example
>>> example.test1([1, 2, 3])
1
2
3
>>> example.test1((4, 5, 6))
4
5
6
>>> example.test2([
... ['a', 'b', 'c'],
... ['d', 'e', 'f']
... ])
a
b
c
d
e
f
>>> example.test3([example.Foo(), example.Foo()])
2
对这种方法的一些评论:
A few comments on this approach:
iterable_converter::convertible
函数可以更改为只允许 python 列表,而不是允许任何支持可迭代协议的类型.但是,扩展可能会因此变得有点不合 Python.- 转换是基于 C++ 类型注册的.因此,注册只需进行一次,因为将在任意数量的接受 C++ 类型作为参数的导出函数上选择相同的注册转换.
- 它不会在
example
扩展命名空间中引入不必要的类型. - 元编程可以允许多维类型递归地注册每个维类型.但是,示例代码已经足够复杂了,所以我不想增加额外的复杂性.
- The
iterable_converter::convertible
function could be changed to only allowing python list, rather than allowing any type that supports the iterable protocol. However, the extension may become slightly unpythonic as a result. - The conversions are registered based on C++ types. Thus, the registration only needs to be done once, as the same registered conversion will be selected on any number of exported functions that accept the C++ type as an argument.
- It does not introduce unnecessary types into the
example
extension namespace. - Meta-programming could allow for multi-dimensional types to recursively register each dimension type. However, the example code is already complex enough, so I did not want to add an additional level of complexity.
替代方法包括:
- 为每个接受
std::vector
的函数创建一个接受boost::python::list
的自定义函数或模板函数.这种方法会导致绑定根据导出的函数数量而不是需要转换的类型数量进行缩放. 使用 Boost.Python
vector_indexing_suite代码>
.*_indexing_suite
类导出一个类型,该类型适应 以匹配 Python 列表或字典的某些语义.因此,python 代码现在必须知道要提供的确切容器类型,从而产生较少的 python 扩展.例如,如果将std::vector<double>
导出为VecDouble
,则生成的 Python 用法将是:
- Create a custom function or template function that accepts a
boost::python::list
for each function accepting astd::vector
. This approach causes the bindings to scale based on the amount of functions being exported, rather than the amount of types needing converted. Using the Boost.Python
vector_indexing_suite
. The*_indexing_suite
classes export a type that is adapted to match some semantics of Python list or dictionaries. Thus, the python code now has to know the exact container type to provide, resulting in a less-pythonic extension. For example, ifstd::vector<double>
is exported asVecDouble
, then the resulting Python usage would be:
v = example.VecDouble()
v[:] = [1, 2, 3]
example.test1(v)
但是,由于确切的类型必须匹配,因此以下方法不起作用,因为导出类仅注册 VecDouble
和 std::vector
之间的转换:
However, the following would not work because the exact types must match, as exporting the class only registers a conversion between VecDouble
and std::vector<double>
:
example.test1([4, 5, 6])
虽然这种方法适用于类型而不是函数,但它会导致较少的 Python 扩展,并使用不必要的类型使 example
命名空间膨胀.
While this approach scales to types rather than functions, it results in a less pythonic extension and bloats the example
namespace with unnecessary types.
这篇关于将 Python 列表输入到使用 Boost Python 接收向量的函数中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!