tuplas - listas por comprensión python
Boost:: Python, la conversión de tuplas a Python funciona, el vector<tuple> no (1)
He estado usando Boost :: Python por un tiempo, y todo siempre resultó bien. Sin embargo, ayer estaba tratando de averiguar por qué un tipo en particular que creía haber registrado (una tupla) me estaba dando errores cuando intentaba acceder a él desde Python.
Resulta que si bien la tupla fue realmente registrada, cuando se intenta acceder a ella a través de un std::vector
envuelto mediante el vector_indexing_suite
esto ya no es suficiente.
Me preguntaba, ¿por qué no está funcionando? ¿Hay alguna manera de hacer funcionar esto? ¿Debería tratar de envolver el vector a mano?
A continuación está 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);
}
Accediendo al resultado .so
via Python resulta 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>
>>>
EDITAR: me he dado cuenta de que el error no ocurre si el 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 TupleToPython
C ++ a Python y convertidores de Python a C ++. Esto esta bien.
Por otro lado, quiere que sus elementos vectoriales sean devueltos por referencia. Pero no hay nada en el lado de Python que pueda servir como referencia para su tupla. Una tupla convertida a Python puede contener los mismos valores, pero está completamente separada de la tupla original de C ++.
Parece que para exportar una tupla por referencia, uno necesitaría crear una suite de indexación para ella, en lugar de convertirla en un convertidor de Python. Nunca he hecho eso 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 expone 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 la tupla, la exportación a través de conversores y NoProxy
parece una alternativa viable a esto.