Różnice między wybraną wersją a wersją aktualną.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
|
boost_python [2008/04/14 19:56] kamituel |
boost_python [2008/04/16 07:24] (aktualna) 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> | ||
| + | #!/usr/bin/python | ||
| import p1 | import p1 | ||
| 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 82: | Linia 101: | ||
| 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 93: | Linia 114: | ||
| } | } | ||
| </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ę klasy wrappera! 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: | ||
| Linia 105: | Linia 126: | ||
| </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>()) | ||
| Linia 120: | Linia 141: | ||
| 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//. | 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: | Skoro mamy już gotowy drugi moduł, przetestujmy go: | ||
| Linia 168: | Linia 189: | ||
| # Ale to nie zadziala: x_ro jest tylko do odczytu! | # Ale to nie zadziala: x_ro jest tylko do odczytu! | ||
| #b.x_ro = "To sie nie zapisze!" | #b.x_ro = "To sie nie zapisze!" | ||
| + | </code> | ||
| + | Po uruchomieniu naszym oczom ukaże się: | ||
| + | <code>$ ./p2.py | ||
| + | AAA A aaa | ||
| + | BBB B bbb | ||
| + | CCC C ccc | ||
| + | BBB B bbb ++ PLUS DZIALA | ||
| + | Zapisalo sie! | ||
| + | Zapisalo sie! | ||
| + | </code> | ||
| + | Widzimy więc, że wszystkie elementy działają poprawnie. Co więcej - po odkomentowaniu niektórych fragmentów - np. próby zapisania do zmiennej tylko do odczytu ''x_ro'' interpreter poinformuje nas o błędzie: | ||
| + | <code>$ ./p2.py | ||
| + | AAA A aaa | ||
| + | BBB B bbb | ||
| + | CCC C ccc | ||
| + | BBB B bbb ++ PLUS DZIALA | ||
| + | Zapisalo sie! | ||
| + | Zapisalo sie! | ||
| + | Traceback (most recent call last): | ||
| + | File "./p2.py", line 39, in <module> | ||
| + | b.x_ro = "To sie nie zapisze!" | ||
| + | AttributeError: can't set attribute | ||
| </code> | </code> | ||
| Linia 175: | Linia 218: | ||
| * Kod który piszemy może być wąskim gardłem aplikacji - warto napisać go w C++ ze względu na wydajność | * 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? | * 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ł | * 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. | + | W tym ostatnim przypadku jedyne co nam będzie potrzebne to pliki nagłówkowe tej bilioteki i trochę minut czasu. |
| - | Weźmy klasy z poprzedniego przykładu, rozdzielmy je na plik nagłówkowy i źródłowy. Źródłowy skompilujmy do postaci biblioteki współdzielonej: | + | 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> | <code> | ||
| $ g++ -Wall -shared p2-shared.cpp -o p2-shared.o | $ g++ -Wall -shared p2-shared.cpp -o p2-shared.o | ||
| Linia 211: | Linia 287: | ||
| } | } | ||
| </code> | </code> | ||
| - | i skompilujmy go **nie zapominając** o linkowaniu z naszą biblioteką ''p3-shared.o'': | + | i skompilujmy go **nie zapominając** o linkowaniu z naszą biblioteką ''p2-shared.o'': |
| <code> | <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 | 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> | </code> | ||
| I gotowe! Możemy teraz uruchomić skrypt z poprzedniego przykładu. A to wszystko bez dostępu do plików źródłowych! | 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]] | ||
| + | |||