Alimenter une liste Python dans une fonction en prenant un vecteur avec Boost Python

j'ai une fonction avec la signature:

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

et je l'ai exposé, mais il ne prend pas en Python listes. J'ai regardé les autres réponses, et la plupart impliquent de changer la fonction pour prendre boost::python::listes, mais je ne veux pas changer la fonction. J'imagine que je peux utiliser le vector_indexing_suite pour écrire un simple wrapper autour de cette fonction, mais j'ai beaucoup de fonctions de cette forme et préfère ne pas écrire un wrapper pour tous. Est-il un moyen pour faire automatiquement une liste Python - > std:: la cartographie vectorielle se produit?

21
demandé sur sehe 2013-04-05 23:36:51

1 réponses

il y a quelques solutions pour accomplir ceci sans avoir à modifier les fonctions originales.

pour accomplir ceci avec une petite quantité de code boilerplate et de transparence vers python, pensez à enregistrer un personnalisé converter. Stimuler.Python utilise des convertisseurs enregistrés entre les types C++ et Python. Certains convertisseurs sont implicitement créés lors de la création de fixations, comme lorsque class_ exporte un type.

ce qui suit complète exemple utilise un iterable_converter type qui permet l'enregistrement des fonctions de conversion d'un type python supportant le python itérable protocole. L'exemple permet des conversions pour:

  • Collection de type: std::vector<double>
  • collection bidimensionnelle de cordes:std::vector<std::vector<std::String> >
  • Collection de type d'utilisateur: 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);
}

usage interactif:

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

quelques commentaires sur ce approche:

  • iterable_converter::convertible la fonction pourrait être changée en n'autorisant que la liste python, plutôt que d'autoriser n'importe quel type qui supporte le protocole itérable. Cependant, l'extension peut devenir légèrement Non rythmique en conséquence.
  • les conversions sont enregistrées en fonction des types C++. Ainsi, l'enregistrement n'a besoin d'être fait qu'une seule fois, car la même conversion enregistrée sera sélectionnée sur n'importe quel nombre de fonctions exportées qui acceptent le type C++ comme un argument.
  • il n'introduit pas de types inutiles dans le example extension d'espace de noms.
  • la méta-programmation pourrait permettre aux types multidimensionnels d'enregistrer récursivement chaque type de dimension. Cependant, le code d'exemple est déjà assez complexe, donc je ne voulais pas ajouter un niveau supplémentaire de complexité.

les autres approches comprennent:

  • créer une fonction personnalisée ou une fonction de modèle qui accepte un boost::python::list pour chaque fonction acceptant un std::vector. Cette approche amène les reliures à une échelle basée sur la quantité de fonctions exportées, plutôt que sur la quantité de types nécessitant une conversion.
  • Utiliser le Boost.Python vector_indexing_suite. *_indexing_suite classes exporter un type qui est adapté pour correspondre à une sémantique de la liste Python ou les dictionnaires. Ainsi, le code python doit maintenant connaître le type exact de conteneur à fournir, dans une extension moins pythonique. Par exemple, si std::vector<double> est exporté en tant que VecDouble, alors L'utilisation de Python résultante serait:

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

    cependant, ce qui suit ne fonctionnerait pas parce que les types exacts doivent correspondre, car exporter la classe n'enregistre qu'une conversion entre VecDouble et std::vector<double>:

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

    alors que cette approche s'adapte à des types plutôt qu'à des fonctions, elle se traduit par une extension moins pythonique et gonfle le example espace de noms inutiles type.

27
répondu Tanner Sansbury 2015-12-01 17:44:26