2017-02-12 95 views
26

Używam Boost :: Python przez jakiś czas, a wszystko zawsze było ok. Jednak wczoraj próbowałem dowiedzieć się, dlaczego dany typ, który myślałem, że zarejestrowałem (krotka) dawał mi błędy, gdy próbowałem uzyskać do niego dostęp z Pythona.Boost :: Python, konwersja krotki do prac w Pythonie, wektor <tuple> nie

Okazuje się, że podczas gdy krotka była rzeczywiście zarejestrowana, próbując uzyskać do niej dostęp poprzez std::vector zapakowaną przez vector_indexing_suite, to już nie wystarczy.

Zastanawiam się, dlaczego to nie działa? Czy jest jakiś sposób, aby to zadziałało? Czy powinienem spróbować ręcznie owijać wektor?

Poniżej jest moje 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); 
} 

Dostęp wynikowy .so pośrednictwem wyników Pythona w:

>>> 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: zdałem sobie sprawę, że błąd nie zdarza się w przypadku stosowania vector_indexing_suite z parametrem NoProxy ustawionym na true. Jednak wolałbym, gdyby nie było to konieczne, ponieważ powoduje, że wyeksportowane klasy są nieintuicyjne w Pythonie.

+0

nie powinien "Tuple makeTuples() { return Tuples {MyTuple()}; } 'be' Tuples makeTuples() { return Krotki(); } zamiast tego? – fedepad

+0

@fedepad Zrobiłem to, abyś mógł wywołać 'makeTuples() [0]' bez wywoływania błędu 'out_of_bounds', ponieważ wtedy wektor byłby pusty i nie zobaczyłbyś błędu krotki. – Svalorzen

Odpowiedz

2

TupleToPython rejestruje konwertery C++-to-Python i konwertery Python-C-C++. Jest okej.

Z drugiej strony, chcesz, aby twoje elementy wektorowe były zwracane przez odniesienie. Ale po stronie Pythona nie ma nic, co mogłoby posłużyć jako odniesienie do twojej krotki. Krotka skonwertowana na Python może zawierać te same wartości, ale jest całkowicie odłączona od oryginalnej krotki C++.

Wygląda na to, że w celu wyeksportowania krotki przez odniesienie, należałoby utworzyć dla niej pakiet indeksujący zamiast konwerterów Python/z /. Nigdy tego nie robiłem i nie mogę zagwarantować, że zadziała.

Oto jak można narazić krotkę jako minimalnie krotki obiekt Pythona (tylko z len() i indeksowaniem). Najpierw zdefiniować kilka funkcji pomocniczych:

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); 
} 

Wtedy wystawiać konkretne krotki:

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>); 
} 

Uwaga te quasi-krotki, w przeciwieństwie do prawdziwych krotek Python, są zmienne (przez C++). Z powodu niezmienności krotki eksportowanie za pośrednictwem konwerterów i NoProxy wygląda na realną alternatywę.

+0

Boost już obsługuje klasy dowolne przez odniesienie. Dlaczego krotka jest inna? – Svalorzen

+0

To na pewno, jeśli wyeksportujesz swoją klasę. Zwróć uwagę, jak wyeksportować wektor za pomocą 'class_ > {" Tuples "}'. –

+0

Więc zasadniczo funkcje konwersji są przydatne tylko wtedy, gdy musisz przekazać coś według wartości, prawda? Trzeba utworzyć klasę szablonów, biorąc typ krotki i rejestrując 'class_' z tym samym interfejsem, co natywną krotkę Pythona, a następnie zadziała. Poprawny? – Svalorzen