27 votes

Boost::Python, la conversion de tuple en Python fonctionne, vector<tuple> ne fonctionne pas

J'utilise Boost::Python depuis un certain temps, et tout s'est toujours bien passé. Cependant, hier, j'ai essayé de comprendre pourquoi un type particulier que je pensais avoir enregistré (un tuple) me donnait des erreurs lorsque j'essayais d'y accéder depuis Python.

Il s'avère que, bien que le tuple ait été enregistré, lorsque l'on tente d'y accéder par le biais d'un fichier de type std::vector enveloppé par l'intermédiaire du vector_indexing_suite ce n'est plus suffisant.

Je me demandais, pourquoi ça ne marche pas ? Y a-t-il un moyen de le faire fonctionner ? Dois-je essayer d'envelopper le vecteur à la main ?

Voici mon MVE :

#include <tuple>
#include <vector>

#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

template <typename T>
struct TupleToPython {
    TupleToPython() {
        boost::python::to_python_converter<T, TupleToPython<T>>();
    }

    template<int...>
    struct sequence {};

    template<int N, int... S>
    struct generator : generator<N-1, N-1, S...> { };

    template<int... S>
    struct generator<0, S...> {
        using type = sequence<S...>;
    };

    template <int... I>
    static boost::python::tuple boostConvertImpl(const T& t, sequence<I...>) {
        return boost::python::make_tuple(std::get<I>(t)...);
    }

    template <typename... Args>
    static boost::python::tuple boostConvert(const std::tuple<Args...> & t) {
        return boostConvertImpl(t, typename generator<sizeof...(Args)>::type());
    }

    static PyObject* convert(const T& t) {
        return boost::python::incref(boostConvert(t).ptr());
    }
};

using MyTuple = std::tuple<int>;
using Tuples = std::vector<MyTuple>;

MyTuple makeMyTuple() {
    return MyTuple();
}

Tuples makeTuples() {
    return Tuples{MyTuple()};
}

BOOST_PYTHON_MODULE(h)
{
    using namespace boost::python;

    TupleToPython<MyTuple>();
    def("makeMyTuple", makeMyTuple);

    class_<std::vector<MyTuple>>{"Tuples"}
        .def(vector_indexing_suite<std::vector<MyTuple>>());
    def("makeTuples", makeTuples);
}

Accès au résultat .so via Python résulte en :

>>> print makeMyTuple()
(0,)
>>> print makeTuples()[0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: No Python class registered for C++ class std::tuple<int>
>>> 

EDIT : Je me suis rendu compte que l'erreur ne se produit pas si le fichier vector_indexing_suite est utilisé avec le NoProxy défini comme vrai. Cependant, je préférerais que cela ne soit pas nécessaire, car cela rend les classes exportées peu intuitives en Python.

2voto

n.m. Points 30344

TupleToPython enregistre les convertisseurs C++ vers Python et les convertisseurs Python vers C++. C'est très bien.

D'autre part, vous voulez que les éléments de votre vecteur soient retournés par référence. Mais il n'y a rien du côté de Python qui puisse servir de référence à votre tuple. Un tuple converti en Python peut contenir les mêmes valeurs, mais il est complètement détaché du tuple C++ d'origine.

Il semble que pour exporter un tuple par référence, il faudrait créer une suite d'indexation pour celui-ci, plutôt que des convertisseurs vers/depuis Python. Je n'ai jamais fait cela et je ne peux pas garantir que cela fonctionnera.

Voici comment on pourrait exposer un tuple comme un objet Python minimal de type tuple (avec seulement len() et l'indexation). Définissez d'abord quelques fonctions d'aide :

template <typename A>
int tuple_length(const A&)
{
    return std::tuple_size<A>::value;
}

template <int cidx, typename ... A>
typename std::enable_if<cidx >= sizeof...(A), boost::python::object>::type
get_tuple_item_(const std::tuple<A...>& a, int idx, void* = nullptr)
{
    throw std::out_of_range{"Ur outta range buddy"};
}

template <int cidx, typename ... A, typename = std::enable_if<(cidx < sizeof ...(A))>>
typename std::enable_if<cidx < sizeof...(A), boost::python::object>::type
get_tuple_item_(const std::tuple<A...>& a, int idx, int = 42)
{
    if (idx == cidx)
        return boost::python::object{std::get<cidx>(a)};
    else
        return get_tuple_item_<cidx+1>(a, idx);
};

template <typename A>
boost::python::object get_tuple_item(const A& a, int index)
{
    return get_tuple_item_<0>(a, index);
}

Puis exposer des tuples spécifiques :

using T1 = std::tuple<int, double, std::string>;
using T2 = std::tuple<std::string, int>;

BOOST_PYTHON_MODULE(z)
{
    using namespace boost::python;

    class_<T1>("T1", init<int, double, std::string>())
      .def("__len__", &tuple_length<T1>)
      .def("__getitem__", &get_tuple_item<T1>);

    class_<T2>("T2", init<std::string, int>())
      .def("__len__", &tuple_length<T2>)
      .def("__getitem__", &get_tuple_item<T2>);
}

Notez que ces quasi-tuples, contrairement aux vrais tuples Python, sont mutables (via C++). En raison de l'immuabilité des tuples, l'exportation via les convertisseurs et les fichiers de type NoProxy semble être une alternative viable à cela.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X