2013-04-05 7 views
14

Mam funkcji z podpisem:Karmienie listę Pythona do funkcji biorąc w wektorze z Boost, Python

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

A ja narażone, ale to nie ma na listach Pythona . Przejrzałem pozostałe odpowiedzi na WR, a większość z nich dotyczy zmiany funkcji na boost :: python :: lists, ale nie chcę zmieniać tej funkcji. Wyobrażam sobie, że mogę użyć funkcji vector_indexing_suite do napisania prostego wrappera wokół tej funkcji, ale mam wiele funkcji tego formularza i raczej nie piszę wrappera dla każdego z nich. Czy istnieje sposób automatycznego tworzenia listy Pythona-> mapowanie std :: wektor?

Odpowiedz

22

Istnieje kilka sposobów na osiągnięcie tego celu bez konieczności modyfikowania oryginalnych funkcji.

Aby osiągnąć ten cel za pomocą niewielkiej ilości kodu standardowego i przejrzystości dla Pythona, należy rozważyć rejestrację custom converter. Boost.Python używa zarejestrowanych konwerterów podczas przechodzenia między typami C++ i Python. Niektóre konwertery są niejawnie tworzone podczas tworzenia powiązań, na przykład gdy eksportuje typ class_.

Poniższy pełny przykład wykorzystuje typ iterable_converter, który pozwala na rejestrację funkcji konwersji z typu pythonowego obsługującego python iterable protocol. Przykład umożliwić konwersje:

  • Kolekcja wbudowany typ: Kolekcja std::vector<double>
  • 2-wymiarowej strun: std::vector<std::vector<std::String> >
  • Collection od typu użytkownika: 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); 
} 

Interactive użycie:

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

Kilka uwag na temat tego podejścia:

  • Funkcja iterable_converter::convertible mogą być zmieniane tylko pozwalając listy Python, zamiast pozwolić dowolnego typu, który obsługuje iterowalny protokołu. Jednak rozszerzenie może w rezultacie stać się nieznacznie niepytotonowe.
  • Konwersje są rejestrowane w oparciu o typy C++. Dlatego rejestrację należy wykonać tylko raz, ponieważ ta sama rejestrowana konwersja zostanie wybrana na dowolnej liczbie wyeksportowanych funkcji, które akceptują typ C++ jako argument.
  • Nie wprowadza niepotrzebnych typów do przestrzeni nazw rozszerzeń example.
  • Meta-programowanie może umożliwić typom wielowymiarowym rekurencyjne rejestrowanie każdego typu wymiaru. Jednak przykładowy kod jest już wystarczająco złożony, więc nie chciałem dodać dodatkowego poziomu złożoności.

Alternatywne rozwiązania obejmują:

  • utworzyć funkcję niestandardową lub funkcję szablonu, który akceptuje boost::python::list dla każdej funkcji przyjmującej do std::vector. To podejście powoduje skalowanie powiązań w zależności od liczby wyeksportowanych funkcji, a nie liczby konwertowanych typów.
  • Korzystanie z Boost.Python vector_indexing_suite. Klasy *_indexing_suite eksportują typ, który jest przystosowany, aby dopasować niektóre semantyki listy lub słowników Pythona. W związku z tym, kod Pythona musi teraz znać dokładny typ kontenera, aby zapewnić, co powoduje mniej pythonic rozszerzenie. Na przykład, jeśli std::vector<double> jest eksportowany jako VecDouble, a następnie otrzymany Wykorzystanie Python byłoby:

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

    Jednak następujące nie będzie działać, ponieważ dokładne typów musi się zgadzać, a eksportu klasę rejestruje jedynie konwersję pomiędzy VecDouble i std::vector<double>:

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

    Takie podejście Wagi do typów zamiast funkcji, powoduje mniej pythonic rozszerzenia i bloats się example nazw z niepotrzebnych typów.

Powiązane problemy