13 lipca 2015

Boost.Python - Pisanie rozszerzeń dla python-a w C++

Boost.Python pozwala na wykorzystanie klas i metod zaimplementowanych w C++ w Pythonie i odwrotnie. Skupiłem się na pierwszej części. Garść przydatnych linków:
Takie mieszanie technologi, niestety sprawia, że szukanie błędów staje się trochę denerwujące, ale już po dwóch godzinach udało mi się uzyskać działający przykład, który mniej więcej jest tym co chciałem uzyskać. Co ciekawe (jak mówi dokumentacji), kod napisany w C++, rzadko będzie wymagał zmian dostosowujących go do współpracy z Boost.Python-em, co uważam jest dużym plusem.
W pierwszej kolejności należy stworzyć plik źródłowy (tutaj SimplePrinter.cpp), z deklaracją wrappera - gdzie mówimy jakie interfejsy będą widoczny w Pythonie. Boost.Python posiada abstrakcję na std::vector, z której tutaj skorzystałem. Później korzystając z takiego modułu, należy stworzyć specjalny obiekt, dzięki któremu można skonwertować np. listę pythonową i skorzystać z danych w niej zawartych w kodzie C++ (przykład na końcu). Kilka ważnych uwag.
  • Należy pamiętać aby nazwa modułu (w BOOST_PYTHON_MODULE) była identyczna z nazwą pliku biblioteki dynamicznej, inaczej podczas importowanie takiego modułu można natknąć się niewiele mówiący błąd.
  • Nazywanie metody "print" sprawiła, że uzyskiwałem błąd, gdy próbowałem z takiej metody skorzystać. Najpewniej jest to związane z jakimś konfliktem, gdyż print (Python 2) jest słowem kluczowym.
#include <iostream>
#include <string>
#include <vector>

struct SimplePrinter
{
    void setWords(std::vector<std::string> words) {
        _words = words;
    }

    size_t count() const {
        return _words.size();
    }

    void show() {
        std::cout << "All: ";
        for (const auto& w : _words) {
            std::cout << w << " ";
        }
        std::cout << std::endl;
    }

private:
    std::vector<std::string> _words;
};

#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
using namespace boost::python;

BOOST_PYTHON_MODULE(simple_printer)
{
    class_<std::vector<std::string>>("VectorOfStrings")
        .def(vector_indexing_suite<std::vector<std::string>>());

    class_<SimplePrinter>("SimplePrinter")
        .def("setWords", &SimplePrinter::setWords)
        .def("count", &SimplePrinter::count)
        .def("show", &SimplePrinter::show);
}
W celu skompilowania przykładu skorzystałem z Pythonowego distutils, które umożliwia budowanie modułów (nie miałem z tym narzędziem wcześniej do czynienia, ale wydaje się bardzo schludnym rozwiązaniem). W pierwszej kolejności należy stworzyć plik setup.py (w moim przypadku w tym samym katalogu, gdzie znajdowały się źródła). Tutaj ponownie, trzeba sprawdzić, czy nazwa modułu zgadza się z tą, która została zadeklarowana w wraperze C++. Co ważne, moja klasa korzysta z nowego standardu C++14, stąd dodatkowa flaga: extra_compile_args.
#!/usr/bin/env python

from distutils.core import setup
from distutils.extension import Extension

setup(name="PackageName",
    ext_modules=[
        Extension(name="simple_printer",
            sources=["SimplePrinter.cpp"],
            libraries=["boost_python"],
            extra_compile_args=["-std=c++14"])
    ])
Budowanie:
$ cd cpp_boost_python
$ ls
setup.py  SimplePrinter.cpp

$ python setup.py build

running build
running build_ext
building 'simple_printer' extension
creating build
creating build/temp.linux-x86_64-2.7
x86_64-linux-gnu-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -fPIC -I/usr/include/python2.7 -c SimplePrinter.cpp -o build/temp.linux-x86_64-2.7/SimplePrinter.o -std=c++14
cc1plus: warning: command line option ‘-Wstrict-prototypes’ is valid for C/ObjC but not for C++
creating build/lib.linux-x86_64-2.7
c++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wl,-Bsymbolic-functions -Wl,-z,relro -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security build/temp.linux-x86_64-2.7/SimplePrinter.o -lboost_python -o build/lib.linux-x86_64-2.7/simple_printer.so
Po zbudowaniu biblioteka będzie znajdować się w podkatalogu build (build/lib.linux-x86_64-2.7/simple_printer.so).
Na sam koniec test działania.
$ cd build/lib.linux-x86_64-2.7
$ ls 
simple_printer.so

$ python
>>> import simple_printer
>>> s = simple_printer.SimplePrinter()
>>> words = simple_printer.VectorOfStrings()
>>> words.extend(w for w in ["first", "second"])
>>> s.setWords(words)
>>> s.count()
2
>>> s.show()
All: first second

Brak komentarzy:

Prześlij komentarz