To jest stara wersja strony!
Kamil Leszczuk, G1SST
Biblioteka Boost Python umożliwia korzystanie z kodu napisanego w C++ z poziomu skryptów Pythona.
Do dyspozycji mamy nie tylko proste funkcje, ale także całe klasy (i ich hierarchie) czy metody klas - wraz z tymi wirtualnymi.
Dobrze, spróbujmy więc opanować podstawy. Weźmy na przykład ten kod C++:
#include <boost/python.hpp> using namespace boost::python; int potega ( int a ) { return a*a; } BOOST_PYTHON_MODULE(p1) { def("skomplikowany_algorytm", &potega); }
Widzimy jednoparametrową funkcję potega. Nie różni się ona niczym innych funkcji napisanych w C/C++. Ciekawsze są linijki znajdujące się bezpośrednio pod nią - dajemy tam znać, że tworzymy moduł Pythona o nazwie p1, który udostępni jedną funkcję o nazwie skomplikowany_algorytm. Zwróćmy uwagę, że nazwa funkcji udostępniona przez moduł Pythona nie musi pokrywać się z nazwą funkcji z C++. Czas na kompilację - należy zapewnić kompilatorowi dostęp do odpowiednich nagłówków (bibliotek boost i Pythona), a także linkować z biblioteką boost python, np.:
g++ -Wall -shared -I/usr/include/boost/ -I/usr/include/python2.5/ /usr/lib/libboost_python-mt.so p1.cpp -o p1.so
Jako wynik powinniśmy uzyskać plik p1.so, który jest właśnie naszym modułem Pythona. Stwórzmy więc skrypt który z tego modułu skorzysta Uwaga: moduł powinien znaleźć się w tym samym katalogu co skrypt, aby interpreter mógł go bezproblemowo znaleźć ( katalog '.' znajduje się w domyślnej ścieżce poszukiwania Pythona).
import p1 print p1.skomplikowany_algorytm(7)
Uruchommy nasz skrypt (pamiętając aby ewentualnie zmienić ścieżkę do interpretatora Pythona), np.:
$ ./skrypt.py 49
Voila! Nasz niesamowicie skomplikowany algorytm zadziałał.
Teraz, kiedy już znamy podstawy, spróbujmy użyć kodu C++ nieco bardziej podobnego do tego spotykanego na codzień. Weźmy taką hierarchię klas:
#include <iostream> class Costam_0 { public: virtual void metoda ( std::string suffix ) = 0; virtual ~Costam_0 () {} }; class Costam_A : public Costam_0 { public: Costam_A() {} Costam_A( std::string nazwa ) : nazwa_(nazwa) {} virtual void metoda ( std::string suffix ) { nazwa_ += " B "+suffix; } std::string daj_nazwe () const { return nazwa_; } void ustaw_nazwe ( const std::string nazwa ) { nazwa_ = nazwa; } protected: std::string nazwa_; }; class Costam_B : public Costam_A { public: Costam_B() {} Costam_B( std::string nazwa ) : Costam_A(nazwa) {} virtual void metoda ( std::string suffix ) { nazwa_ += " B " + suffix; } std::string x_; };
Widzimy trzy klasy. Abstrakcyjną Costam_0, Costam_A po niej dziedziczącą oraz Costam_B dziedziczącą po Costam_A. Jak taką hierarchię udostępnić jako moduł Pythona?
Aby udostępnić klasę Costam_0 należy dopisać kod tego typu:
class Costam_0Wrap : public Costam_0, public wrapper<Costam_0> { virtual void metoda ( std::string suffix ) { this->get_override("metoda")(); } }; /* ..... */ BOOST_PYTHON_MODULE(p2) { class_<Costam_0Wrap, boost::noncopyable>("Costam0") .def("metoda", pure_virtual(&Costam_0::metoda)) ; }
Potrzebujemy sppecjalnego wrappera dla Costam_0 ponieważ twórcy biblioteki uznali, że najlepiej jest gdy pisząc moduł Pythona nie musimy modyfikować istniejącego kodu C++. Wrapper ten nie robi właściwie nic oprócz wołania odpowiedniej metody. Warto zauważyć, że potrzebujemy go właściwie tylko dlatego, że klasa Costam_0 posiada czysto wirtualną metodę. W definicji modułu widzimy, że najpierw deklarujemy chęć udostępnienia klasy - jako nazwę podajemy jednak nazwę modułu! Klasa ta widoczna będzie w Pythonie pod nazwą 'Costam0' i będzie posiadała jedną metodę. Następnie definiujemy jedną metodę 'metoda' zaznaczając, że jest ona czysto wirtualną składową klasy.
Zajmijmy się teraz klasą Costam_A. Dziedziczy ona po Costam_0. Aby ją udostępnić, musimy dopisać poniższy kod w ramach bloku BOOST_PYTHON_MODULE:
class_<Costam_A, bases<Costam_0> >("CostamA") .def(init<std::string>()) .add_property("nazwa_prop", &Costam_A::daj_nazwe, &Costam_A::ustaw_nazwe) ;
Ten kod właściwie sam siebie tłumaczy. Klasa będzie widoczna pod nazwą 'CostamA'. Posiadała będzie *dwa* konstruktory - domyślny bezparametrowy oraz z jednym parameterem typu string (druga linia). Konstruktor domyślny jest standardowo udostępniany, dlatego jawnie tego nie zażądaliśmy. Klasa dziedziczy po Costam_0 - mówi o tym fragment bases<Costam_0>. Dodajemy także składową o nazwie 'nazwa_prop' - mechanizm za nią stojący nie jest właściwie dostępny w C++ - zamiast niego używa się akcesorów. Składowa ta zachowywała się będzie tak jak pole klasy z tą różnicą, że zamiast dokonywać bezpośredniego przypisania do niej wartości, skorzystamy z akcesorów C++ (metody daj_nazwe i ustaw_nazwe). I w końcu - zwróćmy uwagę na brak definicji metody 'metoda' - jako że nasza klasa dziedziczy po 'Costam_0', i w tamtej klasie tę metodę udostępniliśmy, teraz już nie musimy tego robić.
Ostatnia z klas udostępniona może być w ten sposób:
class_<Costam_B, bases<Costam_A> >("CostamB", init<std::string>()) .def_readonly("x_ro", &Costam_B::x_) .def_readwrite("x_rw", &Costam_B::x_) ;
Ponownie, nie definiujemy jeszcze raz elementów z klas stojących wyżej w hierarchii - zajmujemy się tylko nowymi składowymi. Dodajemy pole x_ro klasy, które będzie przyjmowało wartość zmiennej x_, lecz będzie tylko do odczytu - kod Pythona nie będzie mógł zmienić jego wartości. Z kolei własność x_rw jest tym samym, lecz umożliwia dodatkowo zmianę wartości zmiennej. W tym przykładzie te dwie własności wskazują na tę samą zmienną z C++, lecz nie ma to znaczenia.
Klasa ta wprowadza także dodatkową nowość - tym razem nie udostępniamy domyślnego, bezparametrowego konstruktora. Jak uzyskaliśmy ten efekt? Zwróćmy uwagę na pierwszą linijkę - wskazujemy tam, że domyślnie udostępnionym konstruktorem będzie ten pobierający napis.