Różnice między wybraną wersją a wersją aktualną.
Next revision | Previous revision Next revision Both sides next revision | ||
boost_python [2008/04/14 18:54] kamituel utworzono |
boost_python [2008/04/14 21:40] kamituel |
||
---|---|---|---|
Linia 1: | Linia 1: | ||
====== Biblioteka Boost Python ====== | ====== Biblioteka Boost Python ====== | ||
Kamil Leszczuk, G1SST | Kamil Leszczuk, G1SST | ||
+ | |||
+ | ==== Wstęp ==== | ||
Biblioteka Boost Python umożliwia korzystanie z kodu napisanego w C++ z poziomu skryptów Pythona. | 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. | Do dyspozycji mamy nie tylko proste funkcje, ale także całe klasy (i ich hierarchie) czy metody klas - wraz z tymi wirtualnymi. | ||
+ | Wykorzystać można także przeciążone operatory czy pola tylko do odczytu. | ||
+ | \\ Biblioteka Boost Python jest wygodnym narzędziem także ze względu na prostotę swojego interfejsu - zdecydowana większość kodu C++ ma duże szanse współpracować z tą bilblioteką out-of-box, bez niespodzianek i problemów. | ||
+ | |||
+ | ==== Wymagania ==== | ||
+ | |||
+ | Aby rozpocząć pracę z bibliotekami boost::python należy posiadać w systemie (oprócz kompilatora oczywiście): | ||
+ | * Interpreter Pythona | ||
+ | * Pakiet //devel// Pythona - pliki nagłówkowe (jeśli nie zostały dostarczone wraz z instalacją Pythona) | ||
+ | * Biblioteki boost | ||
+ | |||
==== Pierwsze lody ==== | ==== Pierwsze lody ==== | ||
Linia 20: | Linia 32: | ||
BOOST_PYTHON_MODULE(p1) { | BOOST_PYTHON_MODULE(p1) { | ||
- | def("skomplikowany_algorytm", &potega); | + | .def("skomplikowany_algorytm", &potega); |
} | } | ||
</code> | </code> | ||
- | 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++. | + | 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.: | + | |
+ | |||
+ | 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.: | ||
<code shell> | <code shell> | ||
g++ -Wall -shared -I/usr/include/boost/ -I/usr/include/python2.5/ /usr/lib/libboost_python-mt.so p1.cpp -o p1.so | g++ -Wall -shared -I/usr/include/boost/ -I/usr/include/python2.5/ /usr/lib/libboost_python-mt.so p1.cpp -o p1.so | ||
</code> | </code> | ||
- | 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 | + | 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). | + | |
+ | |||
+ | **Uwaga:** moduł powinien znaleźć się w tym samym katalogu co skrypt, aby interpreter mógł go bezproblemowo znaleźć ( katalog bierzący '.' znajduje się w domyślnej ścieżce poszukiwania Pythona). | ||
+ | |||
<code python> | <code python> | ||
import p1 | import p1 | ||
Linia 35: | Linia 53: | ||
print p1.skomplikowany_algorytm(7) | print p1.skomplikowany_algorytm(7) | ||
</code> | </code> | ||
- | Uruchommy nasz skrypt (pamiętając aby ewentualnie zmienić ścieżkę do interpretatora Pythona), np.: | + | Wykonajmy nasz skrypt (pamiętając aby ewentualnie zmienić ścieżkę do interpretatora Pythona, jeżeli w systemie jest inna), np.: |
<code shell> | <code shell> | ||
$ ./skrypt.py | $ ./skrypt.py | ||
Linia 69: | Linia 87: | ||
Costam_B( std::string nazwa ) : Costam_A(nazwa) {} | Costam_B( std::string nazwa ) : Costam_A(nazwa) {} | ||
virtual void metoda ( std::string suffix ) { nazwa_ += " B " + suffix; } | virtual void metoda ( std::string suffix ) { nazwa_ += " B " + suffix; } | ||
+ | Costam_B& operator+ ( std::string x ) { nazwa_ += " ++ " + x; return *this; } | ||
std::string x_; | std::string x_; | ||
}; | }; | ||
</code> | </code> | ||
- | 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? | + | 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: | + | Aby udostępnić klasę ''Costam_0'' należy dopisać kod tego typu: |
<code cpp> | <code cpp> | ||
class Costam_0Wrap : public Costam_0, public wrapper<Costam_0> | class Costam_0Wrap : public Costam_0, public wrapper<Costam_0> | ||
Linia 81: | Linia 100: | ||
virtual void metoda ( std::string suffix ) { | virtual void metoda ( std::string suffix ) { | ||
this->get_override("metoda")(); | this->get_override("metoda")(); | ||
+ | // Dla kompilatora MSVC należy zamienić powyższą linijkę na: | ||
+ | // this->get_override("metoda").ptr(); | ||
} | } | ||
}; | }; | ||
Linia 92: | Linia 113: | ||
} | } | ||
</code> | </code> | ||
- | 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ę. | + | 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 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. | + | 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 funkcję ''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: | + | 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: |
<code cpp> | <code cpp> | ||
class_<Costam_A, bases<Costam_0> >("CostamA") | class_<Costam_A, bases<Costam_0> >("CostamA") | ||
Linia 103: | Linia 124: | ||
</code> | </code> | ||
- | 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. | + | 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>. | + | 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). | + | 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ć. | + | 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: | + | Ostatnia z klas udostępniona może być w taki sposób: |
<code cpp> | <code cpp> | ||
class_<Costam_B, bases<Costam_A> >("CostamB", init<std::string>()) | class_<Costam_B, bases<Costam_A> >("CostamB", init<std::string>()) | ||
.def_readonly("x_ro", &Costam_B::x_) | .def_readonly("x_ro", &Costam_B::x_) | ||
.def_readwrite("x_rw", &Costam_B::x_) | .def_readwrite("x_rw", &Costam_B::x_) | ||
+ | .def(self + std::string()) | ||
; | ; | ||
</code> | </code> | ||
Ponownie, nie definiujemy jeszcze raz elementów z klas stojących wyżej w hierarchii - zajmujemy się tylko nowymi składowymi. | 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. | + | 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. | + | 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. | + | W tym przykładzie te dwie własności wskazują na tę samą zmienną z C++, lecz równie dobrze mogłyby to być zupełnie różne obiekty - nie ma to znaczenia. |
+ | |||
+ | Dodatkowo widzimy jeszcze jedną możliwość biblioteki ''boost::python'' - udostępnianie przeciążonych operatorów C++ - tutaj na przykładzie operatora '+' z parametrem typu //string//. | ||
Klasa ta wprowadza także dodatkową nowość - tym razem nie udostępniamy domyślnego, bezparametrowego konstruktora. Jak uzyskaliśmy ten efekt? | 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. | + | Zwróćmy uwagę na pierwszą linijkę - wskazujemy tam, że domyślnie udostępnionym konstruktorem będzie ten pobierający napis (''init<std::string>''). |
+ | |||
+ | Skoro mamy już gotowy drugi moduł, przetestujmy go: | ||
+ | <code python> | ||
+ | #!/usr/bin/python | ||
+ | import p2 | ||
+ | |||
+ | # Odziedziczylismy po klasie Costam_B, przeciazajac metode 'metoda' | ||
+ | class CostamC(p2.CostamB): | ||
+ | def metoda(self, suffix): | ||
+ | self.nazwa_prop += " C " + suffix | ||
+ | |||
+ | |||
+ | # Stworzmy obiekty kazdej z klas: | ||
+ | a = p2.CostamA("AAA") | ||
+ | b = p2.CostamB("BBB") | ||
+ | c = CostamC("CCC") | ||
+ | |||
+ | # To nie zadziala! Konstruktor bezparametrowy nie zostal udostepniony | ||
+ | # b_prim = p2.CostamB() | ||
+ | |||
+ | # Wolamy metode 'metoda'. Jako ze jest ona wirtualna, | ||
+ | # powinnismy uzyskac nieco inny efekt za kazdym razem... | ||
+ | a.metoda("aaa"); | ||
+ | b.metoda("bbb"); | ||
+ | c.metoda("ccc"); | ||
+ | |||
+ | # ... i tak faktycznie jest: | ||
+ | print a.nazwa_prop | ||
+ | print b.nazwa_prop | ||
+ | print c.nazwa_prop | ||
+ | |||
+ | # Przetestujmy takze operator dodawania dla klasy CostamB | ||
+ | # (zadziala takze dla klasy CostamC jako ze dziedziczy po CostamB) | ||
+ | b = b + "PLUS DZIALA" | ||
+ | print b.nazwa_prop | ||
+ | |||
+ | # Zostaly jeszcze pola read-only i read-write dla zmiennej x: | ||
+ | b.x_rw = "Zapisalo sie!" | ||
+ | print b.x_rw | ||
+ | print b.x_ro | ||
+ | # Ale to nie zadziala: x_ro jest tylko do odczytu! | ||
+ | #b.x_ro = "To sie nie zapisze!" | ||
+ | </code> | ||
+ | |||
+ | |||
+ | ==== Z życia wzięte ==== | ||
+ | Czyli działa - po co jednak aż tak się gimnastykować skoro można napisać te klasy od razu w Pythonie? | ||
+ | Powodów jest kilka: | ||
+ | * Kod który piszemy może być wąskim gardłem aplikacji - warto napisać go w C++ ze względu na wydajność | ||
+ | * Mamy już kod w C++ - po co przepisywać go od nowa? | ||
+ | * Aplikację napisać w C++ a GUI w, dużo przyjemniejszym, Pythonie | ||
+ | * Mamy dostęp do biblioteki C++, ale nie mamy do niej źródeł | ||
+ | W tym ostatnim przypadku jedyne co nam będzie potrzebne to pliki nagłówkowe tej bilioteki i parę minut czasu. | ||
+ | Weźmy klasy z poprzedniego przykładu, rozdzielmy je na plik nagłówkowy i źródłowy. | ||
+ | |||
+ | Plik nagłówkowy wyglądać może tak: | ||
+ | <code cpp> | ||
+ | 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 ); | ||
+ | virtual void metoda ( std::string suffix ); | ||
+ | std::string daj_nazwe () const; | ||
+ | void ustaw_nazwe ( const std::string nazwa ); | ||
+ | protected: | ||
+ | std::string nazwa_; | ||
+ | }; | ||
+ | |||
+ | class Costam_B : public Costam_A { | ||
+ | public: | ||
+ | Costam_B(); | ||
+ | Costam_B( std::string nazwa ); | ||
+ | virtual void metoda ( std::string suffix ); | ||
+ | Costam_B& operator+ ( std::string x ); | ||
+ | |||
+ | std::string x_; | ||
+ | }; | ||
+ | </code> | ||
+ | |||
+ | Plik źródłowy skompilujmy do postaci biblioteki współdzielonej: | ||
+ | <code> | ||
+ | $ g++ -Wall -shared p2-shared.cpp -o p2-shared.o | ||
+ | </code> | ||
+ | Teraz stwórzmy plik ''p2.cpp'' z definicją naszego modułu Pythona: | ||
+ | <code cpp> | ||
+ | #include "p2-shared.h" | ||
+ | #include <boost/python.hpp> | ||
+ | using namespace boost::python; | ||
+ | |||
+ | 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)) | ||
+ | ; | ||
+ | class_<Costam_A, bases<Costam_0> >("CostamA") | ||
+ | .def(init<std::string>()) | ||
+ | .add_property("nazwa_prop", &Costam_A::daj_nazwe, &Costam_A::ustaw_nazwe) | ||
+ | ; | ||
+ | class_<Costam_B, bases<Costam_A> >("CostamB", init<std::string>()) | ||
+ | .def_readonly("x_ro", &Costam_B::x_) | ||
+ | .def_readwrite("x_rw", &Costam_B::x_) | ||
+ | .def(self + std::string()) | ||
+ | ; | ||
+ | } | ||
+ | </code> | ||
+ | i skompilujmy go **nie zapominając** o linkowaniu z naszą biblioteką ''p2-shared.o'': | ||
+ | <code> | ||
+ | g++ -Wall -shared -I/usr/include/boost/ -I/usr/include/python2.5/ /usr/lib/libboost_python-mt.so p2-shared.o p2.cpp -o p2.so | ||
+ | </code> | ||
+ | I gotowe! Możemy teraz uruchomić skrypt z poprzedniego przykładu. A to wszystko bez dostępu do plików źródłowych! | ||
+ | \\ (w rzeczywistej sytuacji plik p2-shared.o byłby biblioteką której źródeł nie mamy - jak widać nawet pomimo tego udało się wystawić ją jako moduł Pythona) | ||
+ | |||
+ | ==== Więcej informacji ==== | ||
+ | |||
+ | * [[http://www.boost.org/libs/python|Witryna biblioteki]] | ||
+ | * [[http://wiki.python.org/moin/boost.python|Wiki na stronie pythona]] |