Feeding a Python list into a function taking in a vector with Boost Python

前端 未结 1 627
既然无缘
既然无缘 2020-12-01 01:53

I\'ve got a function with the signature:

function(std::vector vector);

And I\'ve exposed it, but it doesn\'t take in Python l

相关标签:
1条回答
  • 2020-12-01 02:01

    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.

    0 讨论(0)
提交回复
热议问题