Подача списка Python в функцию, принимающую вектор с помощью Boost Python

У меня есть функция с сигнатурой:

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

И я разоблачил его, но он не принимает в списках Python. Я просмотрел другие ответы SO, и большинство из них связано с изменением функции для загрузки boost:: python:: lists, но я не хочу менять функцию. Я предполагаю, что я могу использовать vector_indexing_suite, чтобы написать простую оболочку вокруг этой функции, но у меня есть много функций этой формы и, скорее, не буду писать обертку для каждого из них. Есть ли способ автоматического создания списка Python → std::vector?

Ответы

Ответ 1

Существует несколько решений для этого, не изменяя исходные функции.

Чтобы выполнить это с небольшим количеством кода шаблона и прозрачности для python, рассмотрите возможность регистрации custom converter. Boost.Python использует зарегистрированные конвертеры при переходе между типами С++ и Python. Некоторые преобразователи неявно создаются при создании привязок, например, когда class_ экспортирует тип.

В следующем полном примере используется тип iterable_converter, который позволяет регистрировать функции преобразования из типа python, поддерживая итеративный протокол python. В примере разрешены преобразования для:

  • Коллекция встроенного типа: std::vector<double>
  • 2-мерный набор строк: 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 memory block, and assign
    // its handle to the converter convertible variable.  The C++
    // container is populated by passing the begin and end iterators of
    // the python object to the container 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

Несколько комментариев по этому подходу:

  • Функция iterable_converter::convertible может быть изменена только для разрешения списка python, а не для разрешения любого типа, поддерживающего итеративный протокол. Тем не менее, в результате расширение может стать немного нерентичным.
  • Конверсии регистрируются на основе типов С++. Таким образом, регистрация должна выполняться только один раз, поскольку одно и то же зарегистрированное преобразование будет выбрано на любом количестве экспортированных функций, которые принимают тип С++ в качестве аргумента.
  • Он не вводит ненужные типы в пространство имен example.
  • Мета-программирование может позволить многомерным типам рекурсивно регистрировать каждый тип измерения. Однако примерный код уже достаточно сложный, поэтому я не хотел добавлять дополнительный уровень сложности.

Альтернативные подходы включают:

  • Создайте пользовательскую функцию или функцию шаблона, которая принимает boost::python::list для каждой функции, принимающей std::vector. Этот подход заставляет привязки масштабироваться в зависимости от объема экспортируемых функций, а не от количества типов, которые нужно преобразовать.
  • Использование Boost.Python vector_indexing_suite. Классы *_indexing_suite экспортируют тип, который адаптирован для соответствия семантике списка или словарей Python. Таким образом, код python теперь должен знать точный тип контейнера для предоставления, что приводит к менее питоническому расширению. Например, если 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 и раздувает пространство имен example с ненужными типами.