将Python列表提供给使用Boost Python的向量中的函数 [英] Feeding a Python list into a function taking in a vector with Boost Python

查看:192
本文介绍了将Python列表提供给使用Boost Python的向量中的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带签名的函数:

  function(std :: vector< double> vector); 

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

解决方案

有几个解决方案可以无需修改原始功能即可实现此目的。



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



以下完整示例使用一个 iterable_converter 类型,允许从支持 python iterable protocol 。该示例启用以下转换:




  • 内置类型的集合: 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样机模型。
class foo {};

//测试功能演示功能。

void test1(std :: vector< double> values)
{
for(auto&& value:values)
std :: cout<< ;值<<的std :: ENDL;
}

void test2(std :: vector< std :: vector< std :: string>> values)
{
for(auto&&内部:值)
for(auto&& value:inner)
std :: cout<<值<<的std :: ENDL;
}


void test3(std :: list< foo> values)
{
std :: cout<< values.size()<<的std :: ENDL;
}

/// @brief允许从
/// python可迭代类型注册转换的类型。
struct iterable_converter
{
/// @note将转换器从python可交换类型注册到
///提供的类型。
模板< typename容器>
iterable_converter&
from_python()
{
boost :: python :: converter :: registry :: push_back(
& iterable_converter :: convertible,
& iterable_converter :: construct< Container>,
boost :: python :: type_id< Container>());

//支持链接。
返回* this;
}

/// @brief检查PyObject是否可迭代。
static void * convertible(PyObject * object)
{
返回PyObject_GetIter(对象)? object:NULL;
}

/// @brief将可迭代PyObject转换为C ++容器类型。
///
///容器概念要求:
///
/// * Container :: value_type是CopyConstructable。
/// *可以构造容器并使用两个迭代器填充容器。
/// I.e.容器(开始,结束)
模板< typename容器>
static void construct(
PyObject * object,
boost :: python :: converter :: rvalue_from_python_stage1_data * data)
{
namespace python = boost :: python;
//对象是一个借用的引用,因此创建一个句柄,表明它是
//借用于正确的引用计数。
python :: handle<>处理(蟒::借用(对象));

//获取转换器已为C ++类型分配
//的内存块句柄。
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;

//将C ++类型分配到转换器的内存块中,并将
//句柄分配给转换器的可转换变量。通过将
// python对象的开始和结束迭代器传递给容器的构造函数来填充C ++
//容器。
new(storage)Container(
iterator(python :: object(handle)),// begin
iterator()); //结束
data-> convertible = storage;
}
};

BOOST_PYTHON_MODULE(示例)
{
namespace python = boost :: python;

//注册可互换转化。
iterable_converter()
//内置类型。
.from_python< std :: vector< double> >()
//每个维度都需要可转换。
.from_python< std :: vector< std :: string> >()
.from_python< std :: vector< std :: vector< std :: string> > >()
//用户类型。
.from_python< std :: list< foo> >()
;

python :: class_< foo>(Foo);

python :: def(test1,& test1);
python :: def(test2,& test2);
python :: def(test3,& test3);
}

交互式使用:



< pre class =lang-python prettyprint-override> >>>导入示例
>>> 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

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




  • iterable_converter :: convertible 函数可以更改为仅允许python列表,而不是允许任何支持可迭代协议的类型。但是,扩展可能会因此而变得略微不合理。

  • 转换是根据C ++类型注册的。因此,只需要进行一次注册,因为在接受C ++类型作为参数的任意数量的导出函数上将选择相同的注册转换。

  • 它不会引入不必要的类型为示例扩展名称空间。

  • 元编程可以允许多维类型递归地注册每个维度类型。但是,示例代码已经足够复杂,所以我不想添加额外的复杂程度。






替代方法包括:




  • 创建一个接受提升的自定义函数或模板函数:: python :: list 用于接受 std :: vector 的每个函数。这种方法会导致绑定根据导出的函数数量而不是需要转换的类型数量进行扩展。

  • 使用Boost.Python vector_indexing_suite * _ indexing_suite 类导出 adapt 的类型以匹配Python列表或词典的某些语义。因此,python代码现在必须知道要提供的确切容器类型,从而产生较少的pythonic扩展。例如,如果将 std :: vector< double> 导出为 VecDouble ,则生成的Python用法将为:

      v = example.VecDouble()
    v [:] = [1,2 ,3]
    example.test1(v)

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

      example.test1([4,5,6])

    虽然这种方法可以扩展到类型而不是函数,但它会导致较少的pythonic扩展,并且会使示例膨胀带有不必要类型的命名空间。



I've got a function with the signature:

function(std::vector<double> 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.

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.

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:

  • Collection of built-in type: std::vector<double>
  • 2-dimensional collection of strings: std::vector<std::vector<std::String> >
  • Collection of user type: 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);
}

Interactive usage:

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

  • 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.

Alternative approaches include:

  • Create a custom function or template function that accepts a boost::python::list for each function accepting a std::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, if std::vector<double> is exported as VecDouble, then the resulting Python usage would be:

    v = example.VecDouble()
    v[:] = [1, 2, 3]
    example.test1(v)
    

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

    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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆