【问题标题】:Feeding a Python list into a function taking in a vector with Boost Python将 Python 列表输入到使用 Boost Python 接收向量的函数中
【发布时间】:2013-03-28 08:27:30
【问题描述】:

我有一个带有签名的函数:

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

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

【问题讨论】:

    标签: boost iterator boost-python


    【解决方案1】:

    有一些解决方案可以在不修改原始功能的情况下完成此操作。

    要使用少量样板代码和对 python 的透明度来完成此操作,请考虑注册custom converter。 Boost.Python 在 C++ 和 Python 类型之间转换时使用已注册的转换器。有些转换器是在创建绑定时隐式创建的,例如class_ 导出类型时。

    以下完整示例使用iterable_converter 类型,该类型允许从支持python iterable protocol 的python 类型注册转换函数。该示例启用以下转换:

    • 内置类型集合:std::vector&lt;double&gt;
    • 二维字符串集合:std::vector&lt;std::vector&lt;std::String&gt; &gt;
    • 用户类型集合:std::list&lt;foo&gt;
    #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
    

    一些关于这种方法的cmets:

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

    替代方法包括:

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

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

      但是,由于确切的类型必须匹配,因此以下方法不起作用,因为导出类只会注册 VecDoublestd::vector&lt;double&gt; 之间的转换:

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

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

    【讨论】:

    • 多么好的方法啊。在我的例子中,我还想支持列表元素和 C++ 容器内容之间的隐式转换,这段代码可以轻松处理。
    • 有什么想法可以通过使用“视图”或类似的东西来避免元素复制?就像使函数取一个而不是构造的向量一样。如果 Python 提供了一个连续的内存范围,我可以看到它正在工作......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-03
    • 2022-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多