将Python列表添加到使用Boost Python的向量中的函数中

vic*_*tor 23 boost iterator boost-python

我有一个带签名的功能:

function(std::vector<double> vector);
Run Code Online (Sandbox Code Playgroud)

而且我已经暴露了它,但它没有接受Python列表.我已经查看了其他的SO答案,并且大多数都涉及更改函数以接受boost :: python :: lists,但我不想更改函数.我想我可以使用vector_indexing_suite为这个函数编写一个简单的包装器,但是我有很多这种形式的函数,而不愿为每一个函数编写一个包装器.有没有办法自动生成Python list-> std :: vector映射?

Tan*_*ury 30

有一些解决方案可以实现这一点,而无需修改原始功能.

要通过少量的样板代码和python透明度来实现这一点,请考虑注册自定义converter.Boost.Python在C++和Python类型之间使用已注册的转换器.创建绑定时会隐式创建某些转换器,例如class_导出类型时.

以下完整示例使用一种iterable_converter类型,该类型允许从支持python可迭代协议的python类型注册转换函数.该示例启用以下转换:

  • 内置类型的集合: std::vector<double>
  • 二维字符串集合: std::vector<std::vector<std::String> >
  • 用户类型集合: 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);
}
Run Code Online (Sandbox Code Playgroud)

互动用法:

>>> 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
Run Code Online (Sandbox Code Playgroud)

关于这种方法的一些评论:

  • iterable_converter::convertible函数可以更改为仅允许python列表,而不是允许任何支持可迭代协议的类型.但是,扩展可能会因此变得略微不合理.
  • 转换是基于C++类型注册的.因此,只需要进行一次注册,因为将在接受C++类型作为参数的任意数量的导出函数上选择相同的注册转换.
  • 它不会在example扩展名称空间中引入不必要的类型.
  • 元编程可以允许多维类型递归地注册每个维度类型.但是,示例代码已经足够复杂,所以我不想添加额外的复杂性.

替代方法包括:

  • 创建一个自定义函数或模板函数,接受一个接受a boost::python::list的每个函数std::vector.此方法会导致绑定根据导出的函数数量进行扩展,而不是需要转换的类型数量.
  • 使用Boost.Python vector_indexing_suite.这些*_indexing_suite类导出一个类型,该类型适合匹配Python列表或字典的某些语义.因此,python代码现在必须知道要提供的确切容器类型,从而产生较少的pythonic扩展.例如,如果std::vector<double>导出为VecDouble,则生成的Python用法将为:

    v = example.VecDouble()
    v[:] = [1, 2, 3]
    example.test1(v)
    
    Run Code Online (Sandbox Code Playgroud)

    但是,以下方法不起作用,因为确切的类型必须匹配,因为导出类只注册VecDouble和之间的转换std::vector<double>:

    example.test1([4, 5, 6])
    
    Run Code Online (Sandbox Code Playgroud)

    虽然这种方法可以扩展到类型而不是函数,但它会导致较少的pythonic扩展,并使example命名空间变得不必要.