He estado usando Boost::Python por un tiempo y todo siempre salió bien. Sin embargo, ayer estaba tratando de averiguar por qué un tipo en particular que pensé que había registrado (una tupla) me estaba dando errores cuando intentaba acceder a él desde Python.
Resulta que, si bien la tupla estaba realmente registrada, al intentar acceder a ella a través de un std::vector envuelto a través de vector_indexing_suite esto ya no es suficiente.
Me preguntaba, ¿por qué no funciona? ¿Hay alguna manera de hacer funcionar esto? ¿Debería intentar envolver el vector a mano?
A continuación se muestra mi 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); } Acceder al .so resultante a través de Python da como resultado:
>>> 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> >>> EDITAR: Me di cuenta de que el error no ocurre si vector_indexing_suite se usa con el parámetro NoProxy establecido en verdadero. Sin embargo, preferiría que esto no fuera necesario, ya que hace que las clases exportadas no sean intuitivas en Python.
TupleToPython registra convertidores de C++ a Python y convertidores de Python a C++. Esto esta bien.
Por otro lado, desea que sus elementos vectoriales se devuelvan por referencia. Pero no hay nada en el lado de Python que pueda servir como referencia para tu tupla. Una tupla convertida a Python puede contener los mismos valores, pero está completamente separada de la tupla C++ original.
Parece que para exportar una tupla por referencia, sería necesario crear una suite de indexación para ella, en lugar de convertidores a/desde Python. Nunca lo he hecho y no puedo garantizar que funcione.
Así es como se podría exponer una tupla como un objeto Python mínimo similar a una tupla (con solo len() e indexación). Primero defina algunas funciones auxiliares:
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); }Luego exponga tuplas específicas:
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>); } Tenga en cuenta que estas cuasi-tuplas, a diferencia de las tuplas reales de Python, son mutables (a través de C++). Debido a la inmutabilidad de las tuplas, la exportación a través de convertidores y NoProxy parece una alternativa viable a esto.