17. Wbudowywanie i rozszerzanie Pythona przy pomocy C/C++
Python zawiera liczne modułów, dzięki którym jest udostępniona ogromna różnorodność jego cech. Wiele niezależnych modułów jest także dostępnych, w tym zapewniających obsługę dla CORBA, XML i szerokiego zestawu funkcji serwera WWW. Może się zdarzyć, że Python i jego moduły mogą nie sprostać naszym wymaganiom, albo ponieważ niezbędna funkcja nie jest realizowana, albo też wydajność skryptu Pythona jest niezadowalająca.
Te dwa problemy mogą być jednocześnie rozwiązane poprzez utworzenie modułu „rozszerzenia” w C/C++, który można wywołać ze skryptu Pythona. W ten sposób dosłownie dokonujemy rozszerzenia Pythona, nadając programom dostęp do nowych możliwości i typów obiektów, które nie są włączone w jego podstawowym środowisku. Niekiedy rozszerzenie polega na poprawieniu wydajności istniejących funkcji. Co więcej, moduły mogą być użyte do zdefiniowania nowych typów obiektów, jak też i nowych funkcji.
Python udostępnia bogaty zestaw interfejsów programowania aplikacji (API), które ułatwiają opracowanie modułów rozszerzenia. Owe interfejsy API pozwalają także programom w C/C++ na wywołanie skryptów Pythona, poprzez wbudowanie interpretera Pythona do istniejącego programu w języku C/C++. Najczęściej programy, które mają wbudowany interpreter Pythona, także rozszerzają Pythona, umożliwiając skryptom na dostęp do funkcji zadeklarowanych w programie macierzystym (ang. host program).
W tym rozdziale, rozszerzymy Pythona poprzez utworzenie kilku modułów rozszerzenia w C/C++. Rozpoczniemy od użycia SWIG (Simplified Wrapper Interface Generator — uproszczony generator interfejsu otoczki) dla opracowania kilku prostych modułów. Utworzymy następnie bardziej zaawansowane moduły rozszerzenia poprzez bezpośrednie użycie interfejsów programowanie Pythona API C. Na zakończenie, wykorzystamy interfejs API do wbudowania Pythona do programu macierzystego w C/C++. Umożliwimy Pythonowi na wywołania zwrotne (ang. callback) do programu macierzystego poprzez przekształcenie programu macierzystego w moduł rozszerzenia.
Rozszerzenie Pythona za pomocą modułu rozszerzenia C/C++
Typowy scenariusz rozszerzenia jest pokazany powyżej. Program wykonywalny w Pythonie ładuje bibliotekę interpretera Pythona, która z kolei wykonuje skrypt Pythona. W którymś momencie skrypt importuje moduł rozszerzenia. Interpreter Pythona dynamicznie ładuje bibliotekę modułu rozszerzenia oraz inicjalizuje ją. Następnie funkcje i typy obiektów zdefiniowane w module rozszerzenia stają się dostępne dla skryptu Pythona.
Należy pamiętać, że skrypt Pythona nie wie, że zaimportowany moduł jest modułem rozszerzenia, a nie zwykłym modułem Pythona. Z punktu widzenia skryptu nie ma to znaczenia.
Wbudowanie Pythona do programu macierzystego
Biblioteka interpretera Pythona może być również załadowana do programów C/C++, wzbogacając je potężnymi możliwościami skryptowymi Pythona.
Powyższy diagram pokazuje typowy scenariusz wbudowania Pythona. W tym przykładzie program macierzysty C/C++ załadował bibliotekę interpretera Pythona. Program macierzysty kieruje wywołanie do interpretera Pythona, używając interfejsu C API Pythona do wykonania skryptów Pythona. Te skrypty Pythona mogą skierować wywołanie zwrotne do programu macierzystego korzystając z modułu rozszerzenia.
W tym przypadku, program macierzysty jest także modułem rozszerzenia, jeśli nawet nie jest on dynamicznie załadowany. Możliwe jest również dynamiczne załadowanie innych modułów rozszerzenia, wtedy kiedy interpreter Pythona jest wbudowany do programu macierzystego C/C++.
Opracowanie modułów rozszerzenia w C/C++
Poniższy plan zarysowuje sposób tworzenia w C/C++ modułu rozszerzenia Pythona:
Określić funkcje, struktury i obiekty, które mają być dostępne dla Pythona.
Utworzyć funkcje interfejsowe (często funkcje owijające — wrapper functions), które mogą być wywołane przez interpreter Pythona.
Skompilować funkcje interfejsowe (moduł owijający).
Skonsolidować moduł owijający wraz z biblioteką funkcji docelowych (ang. target function library) i biblioteką Pythona.
Przetestować moduł.
Funkcje interfejsowe, zwane otoczkami (ang. wrappers), mogą być napisane ręcznie lub utworzone automatycznie przez SWIG. Zaczniemy od użycia SWIG dla utworzenia kodu otoczki dla kilku funkcji i struktur. W dalszym ciągu, będziemy tworzyć funkcje owijające poprzez bezpośrednie użycie C API Pythona.
Wymagane oprogramowanie narzędziowe
Dla rozszerzenia lub wbudowania Pythona, należy mieć zainstalowany podstawowy interpreter Pythona oraz biblioteki do opracowywania oprogramowania (ang. development libraries). Są one dostępne w formacie RPM lub formacie .tar pod adresem http://www.python.org. Dla opracowywania rozszerzeń przy użyciu SWIG, trzeba pobrać i zainstalować SWIG, który można znaleźć pod adresem http://www.swig.org.
Interpreter Pythona
Pakiet z Pythonem zawiera programy wykonywalne w Pythonie, interpreter Pythona oraz bibliotekę Pythona. Należy sprawdzić, czy te pliki są typowo zainstalowane (dla wersji przynajmniej 1.5.2) w katalogach, odpowiednio: /usr/bin/python, /usr/lib/python1.5 i /usr/lib/python1.5/config.
Biblioteki do opracowywania oprogramowania w Pythonie
Biblioteki Pythona są typowo zlokalizowane w katalogu /usr/lib/python1.5/config, gdzie można znaleźć libpython1.5.a, Makefile.pre.in oraz python.o. Jeśli brak tych plików, to trzeba będzie zainstalować pakiet oprogramowania (ang. development package) Pythona lub pobrać i zainstalować wersję źródłową z witryny WWW Pythona.
SWIG - uproszczony generator interfejsu otoczki
SWIG może utworzyć otoczki dla TCL, Perl i Pythona i działa w systemach UNIX i Windows. Użyjemy go do automatycznego wytworzenia funkcji owijających. Dostępnych jest kilka wersji SWIG — nasze przykłady były zrobione przy użyciu wersji 1.1 (łata Patch 5). Można pobrać najnowszą wersję pod adresem http://www.swig.org.
$ swig -version
SWIG Version 1.1 (Patch 5)
Copyright (c) 1995-98
University of Utah and the Regents of the University of California
Compiled with c++
W chwili pisania tego rozdziału, wersje 1.3 swig były nadal zawodne, tak więc powinno się pozostać przy ostatniej stabilnej wersji 1.1.
Rozszerzenie Pythona przy użyciu SWIG
Python udostępnia wszechstronny interfejs programowania C (C API), który jest używany zarówno do rozszerzania, jak i wbudowywania Pythona. Jednakże interfejs jest duży i nie w pełni udokumentowany, co utrudnia jego użycie. Często trzeba udostępnić skryptowi Pythona jedną, czy dwie proste funkcje lub struktury. Jest to , z pozoru, proste zadanie, ale dla osiągnięcia tego celu przy użyciu C API, konieczne jest napisanie obszernego kodu. Z myślą o tego typu prostych zadaniach został zaprojektowany SWIG.
SWIG ułatwia rozszerzenie Pythona — tworzy się prosty plik interfejsowy (ang. file interface), określający funkcje, które mają być dostępne z Pythona. Następnie SWIG automatycznie tworzy kod potrzebny do utworzenia interfejsu pomiędzy Pythonem, a podanymi funkcjami. Dyrektywy SWIG pozwalają na dodatkowy nadzór nad procesem budowy otoczki.
Powyższy diagram pokazuje proces wykorzystywany do utworzenia modułu rozszerzenia dla pliku interfejsowego SWIG. SWIG czyta plik interfejsowy i produkuje plik towarzyszący (ang. shadow file). Plik towarzyszący zawiera definicje klasy wspierającej (ang. helper class), które ułatwiają zadanie tworzenia interfejsu ze strukturami języka C. SWIG tworzy również plik źródłowy C, zawierający kod, oparty na zawartości pliku interfejsowego — ten plik zwany jest plikiem owijającym.
Proces tworzenia funkcji C dostępnych dla Pythona jest powszechnie określany przez programistów Pythona jako „owijanie” (ang. wrapping).
Plik owijający utworzony przez SWIG jest kompilowany i konsolidowany wraz z biblioteką interpretera Pythona, produkując współużytkowany moduł rozszerzenia. Plik owijający mógłby być także skonsolidowany bezpośrednio do wersji interpretera Pythona dostosowanego do indywidualnych potrzeb lub skonsolidowany do programu macierzystego, który będzie wbudowywał interpreter Pythona.
I w końcu, Python ładuje plik towarzyszący i moduł rozszerzenia, by zapewnić dostęp do zadeklarowanych w pliku interfejsowym SWIG funkcji i struktur.
Proste funkcje
Wykonamy pierwszy moduł rozszerzający używając SWIG do owinięcia funkcji i struktury sysinfo. Jeśli przeanalizuje się dokumentację systemową dla sysinfo, to można zauważyć, że opisano tam strukturę zwaną sysinfo, wraz z funkcją o tej samej nazwie. Struktura ta jest zdefiniowana w /usr/include/linux/kernel.h, ale sama funkcja nie jest zdefiniowana w żadnym z plików include.
Zgodnie z konwencją, nazwy plików interfejsowych SWIG używają rozszerzenia .i. Utwórzmy zatem nasz pierwszy plik interfejsowy SWIG pod nazwą sysinfo.i. Powinien powstać nowy katalog, zawierający pliki tworzące moduł rozszerzenia — kilka plików będzie użytych dla każdego z powstających modułów. Można zbudować więcej niż jeden moduł w pojedynczym katalogu, ale w tym przykładzie w nowym katalogu utworzymy tylko jeden moduł.
Plik interfejsowy sysinfo.i zawiera następujący tekst:
%module sysinfo
%{
#include <linux/kernel.h>
#include <linux/sys.h>
%}
%include "linux/kernel.h"
%name(getsysinfo) int sysinfo(struct sysinfo *info);
Należy zwrócić uwagę w tym pliku na kilka spraw:
Dyrektywy SWIG są oznaczone przez wiodący symbol procenta (%).
Tekst zawarty pomiędzy %{ i }% jest umieszczony bez interpretacji w utworzonym w C pliku owijającym.
Pierwszy wiersz pliku interfejsowego, %module sysinfo, określa nazwę modułu Pythona, który ma być utworzony.
W tym przykładzie, pierwsze dwie instrukcje #include będą przepisane do utworzonego w C pliku owijającego. Dokumentacja systemowa dla sysinfo podaje, że oba te pliki powinny być dołączone.
Dyrektywy %include mówią SWIG, aby przetworzyć podany plik, tworząc kod owijający dla struktur i funkcji zdefiniowanych w pliku dołączanym.
W tym przykładzie, przetwarza się tylko linux/kernel.h, ponieważ jest to plik, który definiuje strukturę sysinfo. Należy zwrócić uwagę, że SWIG nie posiada pełnego preprocesora C, tak więc czasem zawodzi dla skomplikowanych plików .h.
Przy owijaniu wielu funkcji, opisanych przez istniejące pliki .h, czasami jest konieczne przepisanie plików .h tak, aby usunąć konstrukcje niezrozumiałe dla SWIG. W tym przypadku SWIG nie ma kłopotów z linux/kernel.h.
Wreszcie, podczas generowania pliku towarzyszącego (ang. shadow file) SWIG utworzy klasę zwaną sysinfo, reprezentującą strukturę C sysinfo.
Ponieważ Python używa pojedynczej przestrzeni nazw (ang. namespace) dla obiektów zadeklarowanych na poziomie modułu, nazwa klasy 'sysinfo' jest w konflikcie z nazwą funkcji 'sysinfo'. Dla usunięcia tego konfliktu nazw, używamy dyrektywy %name w celu zmiany nazwy funkcji sysinfo na getsysinfo w pliku towarzyszącym.
Mając utworzony plik interfejsowy sysinfo.i, można użyć SWIG do wytworzenia w C pliku owijającego sysinfo_wrap.c:
$ swig -python -I/usr/include -shadow -make_default sysinfo.i
Generating wrappers for Python
/usr/include/linux/kernel.h : Line 85. Warning. Array member will be read-only.
/usr/include/linux/kernel.h : Line 93. Warning. Array member will be read-only.
SWIG zgłasza dwa ostrzeżenia podczas przetwarzania pliku interfejsowego. Znaczenie tych ostrzeżeń omówimy dalej.
Kilka opcji wiersza poleceń może być użytych w C do nadzoru procesu tworzenia pliku owijającego:
Opcja |
Znaczenie |
-python |
produkuje kod do rozszerzenia Pythona (zamiast TCL czy Perla), |
-I/usr/include |
określa lokalizację, w której należy szukać plików include, |
-shadow |
tworzy plik towarzyszący w Pythonie, |
-make_default |
tworzy domyślny konstruktor dla struktur, |
sysinfo.i |
nazwa pliku interfejsowego. |
W odróżnieniu od kompilatora C/C++, SWIG domyślnie będzie szukał plików dołączanych w bieżącym katalogu roboczym i w katalogu swig_lib. Ponieważ dołączamy linux/kernel.h w sysinfo.i, użyliśmy tutaj opcji wiersza poleceń -I/usr/include. Informuje ona SWIG o tym, że należy szukać plików dołączanych również w katalogu /usr/include.
Opcja -shadow powoduje, że SWIG produkuje plik towarzyszący w Pythonie. Pliki towarzyszące ułatwiają dostęp z Pythona do struktur i funkcji języka C, poprzez zdefiniowanie struktur jako klas Pythona.
SWIG tworzy w C plik owijający sysinfo_wrap.c oraz w Pythonie plik towarzyszący sysinfo.py. Fragment pliku towarzyszącego, sysinfo.py, pokazano poniżej:
# This file was created automatically by SWIG.
import sysinfoc
class sysinfo:
def __init__(self, *args):
self.this = aply(sysinfoc.new_sysinfo,args)
self.thisown = 1
def __del__(self,sysinfoc=sysinfoc):
if self.thisown == 1 :
sysinfoc.delete_sysinfo(self)
__setmethods__ = {
"uptime" : sysinfoc.sysinfo_uptime_set,
"totalram" : sysinfoc.sysinfo_totalram_set,
"freeram" : sysinfoc.sysinfo_freeram_set,
"sharedram" : sysinfoc.sysinfo_sharedram_set,
"bufferram" : sysinfoc.sysinfo_bufferram_set,
"totalswap" : sysinfoc.sysinfo_totalswap_set,
"freeswap" : sysinfoc.sysinfo_freeswap_set,
"procs" : sysinfoc.sysinfo_procs_set,
}
def __setattr__(self,name,value):
if (name == "this") or (name == "thisown"):
self.__dict__[name] = value; return
method = sysinfo.__setmethods__.get(name,None)
if method: return method(self,value)
self.__dict__[name] = value
<snip>
W pliku towarzyszącym pokazanym powyżej, należy zwrócić uwagę na dwa ważne elementy:
Pierwszy to wiersz import sysinfoc. Kiedy plik towarzyszący sysinfo.py jest importowany do Pythona, sysinfo.py oczekuje, że moduł rozszerzenia zostanie zaimportowany jako sysinfoc. Zatem, podczas kompilacji pliku sysinfo_wrap.c trzeba zaimportować go jako sysinfoc, a nie sysinfo.
Drugi element wiąże się z tym, że struktura sysinfo jest reprezentowana przez klasę o tej samej nazwie i zawiera zarówno konstruktor (__init__), jak i destruktor (__del__).
Kompilacja i test pliku owijającego utworzonego przez SWIG
Plik owijający sysinfo_wrap.c w C musi być skompilowany i skonsolidowany zanim Python będzie mógł go użyć. Ze względu na długość pliku owijającego zrezygnowaliśmy z przedstawienia jego pełnego wydruku. Dystrybucja Pythona zawiera szablon pliku Makefile, który może ułatwić zadanie budowania modułu rozszerzenia. Potrzeba skopiować następujące pliki do bieżącego katalogu roboczego (dokładne ścieżki dostępu mogą wyglądać nieco inaczej w zależności od posiadanej instalacji):
/usr/lib/python1.5/config/Makefile.pre.in (szablon pliku Makefile)
/usr/lib/python1.5/config/Setup (szablon pliku sterującego)
Plik Setup definiuje domyślne moduły, które są wbudowane do interpretera Pythona. Plik ten zawiera instrukcje pozwalające utworzyć własny plik Setup do użytku z szablonem pliku Makefile. Dla ułatwienia, zmieniamy nazwę pliku Setup na Setup.doc w katalogu roboczym. Można będzie potem sięgnąć do komentarzy zawartych w Setup.doc dla odnalezienia instrukcji budowania modułów rozszerzenia dla Pythona przy użyciu tego szablonu.
W naszym przykładzie, tworzymy plik Setup.in zawierający dwa poniższe wiersze:
shared
sysinfoc sysinfo_wrap.c
Pierwszy wiersz mówi szablonowi, aby utworzyć moduł współużytkowany, natomiast drugi określa, że moduł sysinfoc powinien być utworzony z pliku źródłowego sysinfo_wrap.c. W poprzednim podrozdziale badaliśmy plik towarzyszący sysinfo.py i zauważyliśmy, że SWIG oczekuje że skompilowany moduł rozszerzenia będzie miał nazwę modułu podaną w pliku interfejsowym sysinfo.i, tyle że z literą 'c' dodaną do nazwy. W związku z tym, w pliku Setup.in podaliśmy sysinfoc jako nazwę modułu współużytkowanego, który ma być utworzony.
Mając gotowy plik Setup.in, tworzymy plik Makefile zgodnie z instrukcjami zawartymi w Makefile.pre.in:
make -f Makefile.pre.in boot
W procesie rozruchu („boot”) Setup.in jest kopiowany na Setup oraz tworzy Makefile. Po wstępnym zbudowaniu Makefile, zmiany dokonane w Setup.in będą ignorowane, ponieważ Makefile będzie czytał plik Setup, zamiast pliku Setup.in.
Można teraz skompilować plik owijający sysinfo w C, przy użyciu polecenia make:
$ make
gcc -fPIC -g -O2 -I/usr/include/python1.5 -I/usr/include/python1.5 -DHAVE_CONFIG_H -c ./sysinfo_wrap.c
gcc -shared sysinfo_wrap.o -o sysinfocmodule.so
$
Testowanie modułu rozszerzenia
Plik owijający sysinfo_wrap.c został teraz skompilowany w moduł współużytkowalny, który może zostać załadowany przez Pythona. Aby przetestować moduł rozszerzenia, należy zwyczajnie uruchomić interpreter Pythona w tym samym katalogu, w którym jest plik sysinfocmodule.so (jeśli nie uruchomi się interpretera w tym samym katalogu, nie będzie mógł wczytać modułu sysinfoc, gdyż ten moduł nie jest uwzględniony w ścieżce dostępu dla Pythona).
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import sysinfo
>>> si = sysinfo.sysinfo()
>>> sysinfo.getsysinfo(si)
0
>>> print si.totalram, si.freeram, si.sharedram
97861632 2396160 38313984
>>> print si.totalswap, si.freeswap, si.procs
139788288 133033984 73
>>>
Instrukcja import sysinfo powoduje załadowanie przez Python pliku towarzyszącego sysinfo.py. Ten z kolei ładuje skompilowany moduł rozszerzenia sysinfocmodule.so poprzez wykonanie powyżej wspomnianej instrukcji import sysinfo.
Następnie, tworzymy obiekt sysinfo za pomocą przypisania si = sysinfo.sysinfo(). Ta instrukcja powoduje odniesienie si do kopii klasy sysinfo zdefiniowanej przez plik towarzyszący sysinfo.py. Jest to równoważne alokacji pamięci dla struktury sysinfo w języku C.
Wywołujemy funkcję sysinfo (której nazwę zmieniliśmy na getsysinfo) za pomocą instrukcji sysinfo.getsysinfo(si). Wartość zwracana przez funkcję to 0, co jest drukowane przez interpreter Pythona. Tak więc, przekazaliśmy do getsysinfo ostatnio utworzoną strukturę sysinfo, dla której zmienna si jest odsyłaczem.
Wreszcie, mam dwie instrukcje print, które drukują niektóre atrybuty struktury sysinfo.
Dostęp do tablic przy użyciu wskaźników SWIG
SWIG ułatwia szybkie dodawanie funkcji w języku C do programu w języku Python. Jeden obszar, w którym SWIG nie działa za dobrze to obsługa tablic. Kiedy uruchomiliśmy SWIG dla sysinfo.i to zobaczyliśmy dwa ostrzeżenia:
$ swig -python -I/usr/include -shadow -make_default sysinfo.i
Generating wrappers for Python
/usr/include/linux/kernel.h : Line 85. Warning. Array member will be read-only.
/usr/include/linux/kernel.h : Line 93. Warning. Array member will be read-only.
Badając /usr/include/linux/kernel.h, zauważamy, że wiersz 85 zawiera następującą deklarację:
unsigned long loads[3]; /* 1, 5, and 15 minute load averages */
Kiedy próbujemy wydrukować wartość atrybutu loads w Pythonie, to otrzymamy następujący wynik:
>>> print si.loads
_80e958c_unsigned_long_p
Zamiast zwrócenia zawartości tablicy, SWIG zwraca wskaźnik do długiej liczby całkowitej bez znaku (ang. unsigned long). Jednym z powodów dla których tak się dzieje, jest brak sposobu określenia indeksu elementu tablicy do którego chcemy mieć dostęp. Jednakże, SWIG pozwala programom Pythona na dostęp do różnorodnych prostych typów tablicowych za pomocą wskaźników. Zawiera plik interfejsowy pointer.i, który definiuje kilka funkcji dostępu do wskaźników. Ten plik znajduje się w katalogu bibliotecznym swig_lib. Można uzyskać dostęp do tych funkcji zwyczajnie dołączając plik interfejsowy do naszego bazowego pliku interfejsowego sysinfo.i:
%module sysinfo
%{
#include <linux/kernel.h>
#include <linux/sys.h>
%}
%include "pointer.i"
%include "linux/kernel.h"
%name(getsysinfo) int sysinfo(struct sysinfo *info);
Można teraz użyć tych funkcji do uzyskania dostępu do tablicy loads. Należy uruchomić SWIG i przebudować plik sysinfocmodule.so wykonując make. Teraz trzeba uruchomić ponownie Pythona, zaimportować sysinfo, utworzyć strukturę sysinfo i wywołać getsysinfo z tą strukturą. Na koniec można wydrukować zawartość loads:
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import sysinfo
>>> si = sysinfo.sysinfo()
>>> sysinfo.getsysinfo(si)
0
>>> for i in range(0,3): print sysinfo.ptrvalue(si.loads,i,"long")
...
224
1664
0
Instrukcja print sysinfo.ptrvalue(si.loads,i,"long") wywołuje funkcję wskaźnikową ptrvalue, która jest zdefiniowana w pointer.i. Funkcja ptrvalue pobiera trzy argumenty: wskaźnik, indeks i opcjonalnie deklarację rzutowania typu wskaźnika (ang. pointer typecast).
Należy zauważyć, że ptrvalue nie obsługuje długich liczb całkowitych bez znaku. Jednakże można uzyskać dostęp do wartości unsigned long, do których tablica loads się odwołuje, poprzez zrzutowanie ich na typ long.
Dodanie metod wirtualnych do struktur
Dostęp do tablicy przy użyciu ptrvalue jest w rzeczywistości mały precyzyjny. Funkcja ptrvalue nie zna rozmiaru tablicy loads i będzie wytrwale próbować dostępu do każdego elementu tablicy, której indeks jej przekazano, nawet jeśli to będzie indeks wykraczający poza koniec tablicy. SWIG pozwala na dodanie dowolnych metod do struktur C/C++.
Ta cecha sprawia, że struktury C wyglądają i działają podobnie do obiektów C++.
Dodajmy metodę do struktury sysinfo pozwalającą na zwrócenie informacji loads. Użyjemy w sysinfo.i dyrektywy SWIG %addmethods:
%module sysinfo
%{
#include <linux/kernel.h>
#include <linux/sys.h>
%}
%include "pointer.i"
%include "linux/kernel.h"
%addmethods sysinfo {
unsigned long getload(int index) {
if(index >= (sizeof(self->loads)/sizeof(unsigned long)) ||
index < 0) {
return 0;
}
return self->loads[index];
}
}
%name(getsysinfo) int sysinfo(struct sysinfo *info);
To dodaje metodę getload do struktury sysinfo. Kiedy funkcja getload jest wykonana, SWIG automatycznie przekaże wskaźnik do stosownej struktury (sysinfo), jako zmienną self. Wskaźnik self, podobny do wskaźnika this w języku C++, odsyła do kopii obiektu, na którym metoda wykonuje operację.
Funkcja getload sprawdza czy indeks mieści się w granicach tablicy loads. Jeśli indeks wykracza poza granice to zwraca 0. Można przetestować metodę getload uruchamiając SWIG, ponownie wykonując make i ładując Pythona tak, jak poprzednio:
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import sysinfo
>>> si = sysinfo.sysinfo()
>>> sysinfo.getsysinfo(si)
0
>>> for index in range(-1,4): print sysinfo.ptrvalue(si.loads,i,"long")
...
0
63008
63104
55328
0
>>>
Jeśli index jest poza zakresem, funkcja getload zwróci 0. Lepszy rozwiązaniem dla getload byłoby zgłosić wyjątek IndexError, kiedy index jest poza dopuszczalnym zakresem.
Zgłaszanie i obsługa wyjątków przy użyciu odwzorowań typów
Jedną z miłych cech języka Python jest obsługa wyjątków. Instrukcje try, except i raise dostarczają potężnego mechanizmu obsługi rzadkich warunków, a przy tym obsługują szybki typowy przebieg sterowania w programie. SWIG obsługuje zarówno tworzenie wyjątków, jak i przetwarzanie wyjątków, które pojawiają się w funkcjach owijających.
Na przykład, funkcja sysinfo zwraca -1, gdy niepoprawny adres struktury sysinfo został przekazany. Funkcje systemowe i biblioteki często używają specyficznej wartości kodu powrotu (ang. return code value) dla wskazania warunków wystąpienia błędu. Przykładowo, większość wywołań funkcji systemowych zwraca -1 dla wskazania jakiegoś błędu. Ta konsekwentnie zwracana wartość (ang. return code) może dostarczyć podstaw do automatycznego tworzenia wyjątku, poprzez zgłaszanie wyjątku, ilekroć jest zwrócona wartość wskazująca na niepowodzenie (ang. failure return code), taka jak -1.
Możemy udoskonalić moduł rozszerzenia sysinfo poprzez zgłoszenie wyjątku, kiedy sysinfo zwróci -1. SWIG ma dyrektywę typemap (odwzorowanie typów), która umożliwia sterowanie w SWIG konwersją danych Pythona do ich reprezentacji w języku C i na odwrót. SWIG także używa odwzorowań typów do określenia sposobu obsługi wyjątku.
Tworzenie odwzorowań typów w SWIG ma czasem w sobie coś z czarnej magii. Odwzorowania typów mogą ułatwić automatyczną konwersję dużych bibliotek funkcji. Jednakże, niewłaściwe odwzorowania typów mogą utworzyć bezużyteczny kod w C. Trzeba uważnie przestudiować dokumentację, dotyczącą odwzorowania typów oraz przykłady dołączone do SWIG, zanim przystąpi się do zbyt ambitnych zadań.
Obecnie SWIG przechodzi proces wprowadzania znaczących zmian mających na celu przygotowanie wydania 2 .Jego autor powiedział, że odwzorowania typów mogą nie zostać zaimplementowane w tej nowej wersji. Jednakże, gdyby istotnie tak się stało, jest całkiem prawdopodobne, że będzie udostępniony jakiś alternatywny mechanizm.
SWIG zawiera również plik interfejsowy exception.i, który dostarcza wygodny, niezależny od języka mechanizm zgłaszania wyjątków. Użyjemy obu tych cech dla dodania obsługi wyjątku do funkcji sysinfo. Nasz nowy plik interfejsowy sysinfo.i wygląda następująco:
%module sysinfo
%{
#include <linux/kernel.h>
#include <linux/sys.h>
#include <string.h>
#include <errno.h>
%}
%include "pointer.i"
%include "linux/kernel.h"
%include "exception.i"
%addmethods sysinfo {
unsigned long getload(int index) {
if(index >= (sizeof(self->loads)/sizeof(unsigned long)) ||
index < 0) {
return 0;
}
return self->loads[index];
}
}
%typemap(python,except) int {
$function
if( $source < 0) {
_SWIG_exception(SWIG_RuntimeError,strerror(errno));
$cleanup
return NULL;
}
}
%name(getsysinfo) int sysinfo(struct sysinfo *info);
Dołączamy <string.h> oraz <errno.h> dla obsługi funkcji strerror. Instrukcja %include "exception.i" importuje do sysinfoc_wrap.c kod do obsługi wyjątków.
Odwzorowania typów w SWIG są zdefiniowane przez trzy parametry: język docelowy, „typ odwzorowania typów” i parametr typu. W tym przykładzie, dodaliśmy odwzorowanie typów typu "except" dla języka Python, dla funkcji zwracających typ całkowity int. Wszystkie funkcje zdefiniowane w pliku interfejsowym, po tej deklaracji będą miały dostosowane odwzorowanie typu.
SWIG wstawia kod obsługi wyjątku do pliku owijającego w miejscu, gdzie normalnie wywołałby stosowną funkcję biblioteczną, która jest właśnie owijana. Obsługa wyjątku może zmodyfikować wartość zwracaną, zgłosić wyjątek lub nic nie zrobić. Jeśli obsługa wyjątku nie zrobi nic, SWIG zwróci do Pythona wartość kodu powrotu bez modyfikacji tak, jak gdyby obsługa wyjątku nie została określona.
Wykorzystaliśmy również kilka makrodefinicji:
$function rozwija się do rzeczywistego wywołania funkcji sysinfo.
$source rozwija się do nazwy zmiennej przechowującej wynik wywołania sysinfo.
$cleanup rozwija się do dowolnego kodu C, potrzebnego dla zwolnienia pamięci użytej w funkcji owijającej.
Jeśli $source jest mniejsze niż zero to funkcja _SWIGexception jest wywołana dla zgłoszenia warunku wystąpienia wyjątku typu SWIGRuntimeError. Funkcja strerror zamienia errno na łańcuch. Makrodefinicja $cleanup zwalnia każdą zaalokowaną pamięć, a funkcja owijająca zwraca NULL. Należy pamiętać, że funkcja owijająca jest wywoływana z Pythona, który oczekuje, że funkcje C zwrócą NULL, kiedy wystąpi wyjątek.
Jeśli $source jest większe lub równe zero, to SWIG zwróci wartość kodu powrotu do Pythona.
Po ponownym przebiegu SWIG i przebudowaniu sysinfocmodule.so, można przetestować obsługę wyjątku, tworząc niepoprawny obiekt sysinfo:
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import sysinfo
>>> badSI = sisinfo.sysinfoPtr(sysinfo.ptrcast(0,"sysinfo_p"))
>>> sysinfo.getsysinfo(badSI)
Traceback (innermost last):
File "<stdin>", line 1, in ?
RuntimeError: Bad address
>>>
Do utworzeni niepoprawnego obiektu sysinfo, używamy funkcji ptrcast, która tworzy wskaźnik C NULL do obiektu typu sysinfo_p. Wywołanie getsysinfo z tym obiektem prowadzi do zgłoszenia wyjątku. Funkcja strerror przekształciła errno do łańcucha "Bad address" (Zły adres).
Trzeba przyznać, że jest to raczej sztuczny przykład , ale pokazuje jak użyć odwzorowań typów dla wyjątków, aby właściwie obsłużyć niepoprawne warunki. Używając tej techniki można cofnąć się i poprawić funkcję getload tak, aby zgłaszała wyjątek, kiedy wartość indeksu jest poza dopuszczalnym zakresem.
Zmodyfikujmy metodę getload w sysinfo.i, jak następuje:
%include "exception.i"
%typemap(python,except) unsigned long {
$function
if(PyErr_Occurred()) {
$cleanup
return NULL;
}
}
%addmethods sysinfo {
unsigned long getload(int index) {
if(index >= (sizeof(self->loads)/sizeof(unsigned long)) ||
index < 0) {
_SWIG_exception(SWIG_ValueError,"Index out of range");
return 0;
}
return self->loads[index];
}
}
Dodaliśmy nowe odwzorowanie typów dla wyjątku dla funkcji, która zwraca wartości typu długa liczba całkowita bez znaku. Funkcja Pythona PyErr_Occurred zwraca TRUE, jeśli wyjątek został zgłoszony — w tym przypadku odwzorowanie typu dla wyjątku (ang. exception typemap) wykonuje makrodefinicję $cleanup, a następnie zwraca NULL.
W funkcji getload dodaliśmy wywołanie do _SWIG_exception. W przykładzie pokazanym powyżej, zwracamy wartość-atrapę 0 po zgłoszeniu warunku dla wystąpienia wyjątku. Jest to ignorowane przez odwzorowanie typów dla wyjątku, które wykrywa zgłoszony wyjątek ponieważ PyErr_Occurred zwraca TRUE.
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import sysinfo
>>> si = sysinfo.sysinfo()
>>> sysinfo.getsysinfo(si)
0
>>> si.getload(-1)
Traceback (innermost last):
File "<stdin>", line 1, in ?
File sysinfo.py", line 9, in getload
val = sysinfoc.sysinfo_getload(self.this,arg0)
ValueError: Index out of range
>>>
Wywołanie getload z niepoprawnym indeksem powoduje zgłoszenie wyjątku typu ValueError.
Obsługa zmiennych przez odsyłacz wykorzystujący odwzorowania typów
W dużym stopniu wyczerpaliśmy już pulę przykładów, demonstrujących użycie struktury sysinfo i funkcji sysinfo. Zmierzymy się z powszechnym problemem obsługi zmiennych poprzez odsyłacz (ang. reference), także znanym jako zmienne wejścia-wyjścia (ang. in/out variables). Użyjemy funkcji getdomainname zadeklarowanej następująco:
int getdomainname(char *name, int len);
Ta funkcja oczekuje na podanie wskaźnika do bufora znakowego oraz długość tego bufora. Wypełni przekazany bufor nazwą domeny (ang. domain name) i zwróci wartość kodu powrotu. Jednakże, Python nie pozwala na przekazanie zmiennej poprzez odsyłacz, czyli w sposób, w jaki zmienna name jest użyta.
Problem można rozwiązać na wiele sposobów. Najprościej jest utworzyć jedno odwzorowanie typu dla bufora name, a inne dla len. Te odwzorowania typów można zastosować dla funkcji getdomainname. W tym przykładzie użyjemy bufora o ustalonej długości 255 bajtów. Oto właściwe modyfikacje pliku sysinfo.i:
%name(getsysinfo) int sysinfo(struct sysinfo *info);
%typemap(python,ignore)
STRINGLEN (
$target = 255;
}
Podobnie jak odwzorowanie typu except omówione powyżej, ignore definiuje odwzorowanie, które steruje konwersją SWIG wartości pomiędzy Pythonem a C. W tym przypadku, odwzorowanie typu ignore działa dwukierunkowo:
sprawia, że argument jest niewidoczny przy wywołaniu funkcji z Pythona,
określa wartość domyślną dla argumentu.
Skrypty Pythona nie mogą dostarczyć wartości dla zignorowanego argumentu, ponieważ zawsze będzie użyta wartość domyślna, określona w pliku interfejsowym. Makrodefinicja SWIG $target rozwija nazwę zmiennej, która będzie przekazana do wywołanej funkcji. Zmiennej zostaje przypisana wartość 255:
%name(getsysinfo) int sysinfo(struct sysinfo *info);
%typemap(python,ignore)
STRINGOUT (char _temp[255]) {
_temp[0] = 0;
$target = _temp;
}
Odwzorowanie typu ignore definiuje odwzorowanie zwane STRINGOUT. Tworzy to bufor znakowy z zerowym znakiem końca (ang. null-terminated character buffer), nazwany _temp, o długości 255 bajtów. Podobnie jak STRINGLEN, odwzorowanie typu STRINGOUT przekaże adres tego bufora tymczasowego do wywołanej funkcji:
%typemap(python,argout)
STRINGOUT {
PyObject *o;
o = Py_BuildValue("s",$source);
if (!$target) {
$target = o;
} else {
$target = t_output_helper($target, o);
}
}
Odwzorowanie typu argout mówi SWIG, w jaki sposób przekształcić wartość argumentu na wartość wyprowadzaną. Innymi słowy, funkcje które mają argumenty STRINGOUT, widzą tymczasowy łańcuch jako wartości wprowadzane, ale SWIG musi je przekształcić na wartości wyprowadzane w celu ich zwrócenia do Pythona.
W tym przykładzie, makrodefinicja $source rozwija nazwę tymczasowej tablicy znaków, która jest zaalokowana przez omówione powyżej odwzorowanie typu ignore. Funkcja Pythona Py_BuildValue przekształca tablicę znakową na łańcuch Pythona.
Na koniec, wartość łańcucha Pythona jest zwrócona bezpośrednio (jeśli wartość $target rozwija się do NULL) lub dołączona do bieżącej wartości zwracanej, jako dodatkowy składnik wielokrotki (ang. tuple):
%apply STRINGOUT {char *name};
%apply STRINGLEN {int len};
int getdomainname(char *name, int len);
%clear STRINGOUT, char *name;
%clear STRINGLEN, int len;
Zdefiniowaliśmy dwa odwzorowania typów jako STRINGLEN i STRINGOUT, w miejsce odpowiednio int i char *. Dowolna funkcja zdefiniowana po deklaracji tych odwzorowań typów używałaby właśnie takich odwzorowań typów dla każdego parametru typu int i char *. Ponieważ nazwaliśmy je używając dowolnej (ale unikatowej) nazwy, możemy zastosować je selektywnie do funkcji, wykorzystując, jak pokazano powyżej, dyrektywę SWIG %apply.
Na przykład, dyrektywa %apply STRINGOUT {char *name} powoduje, że odwzorowanie typu STRINGOUT będzie zastosowane do dowolnej kolejnej deklaracji funkcji z argumentem typu char *name.
Na koniec, dyrektywa %clear STRINGOUT, char *name dezaktywuje stosowanie odwzorowania typów, tak więc kolejne funkcje z argumentem typu char *name nie będą poddane działaniu odwzorowaniu typu STRINGOUT.
Należy teraz przebudować sysinfo.i, wykonać polecenie make, tworzące plik sysinfocmodule.so i wykonać następujący test:
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import sysinfo
>>> sysinfo.getdomainname()
(0, 'murkworks.com')
>>>
Należy zauważyć, że funkcja getdomainname (nazwana tak z Pythona) nie pobiera żadnego argumentu. Wynika to stąd, że oba argumenty posiadały odwzorowanie typu ignore, które spowodowało, iż SWIG ukrył argumenty z Pythona i ustawił domyślną wartość do przekazania dla funkcji C getdomainname.
Wartość zwracana z funkcji Pythona getdomainname jest dwuelementową wielokrotką. Pierwszy element jest kodem zwracanym z funkcji C getdomainname. Drugi element jest argumentem name.
Tworzenie nowych typów obiektów przy pomocy SWIG
Dyrektywa SWIG %addmethods pozwala na dodanie dowolnej metody do dowolnej struktury. Można również powiązać nazwy specjalnych method języka Pythona ze strukturami. Dodanie specjalnych metod takich jak __getitem__ do jakiejś struktury pozwala tej strukturze obsługiwać iterację przy użyciu operatora Pythona for. Inne specjalne metody obsługują porównanie obiektu, mieszanie (ang. hashing) i reprezentację łańcucha.
W tym podrozdziale, utworzymy ze struktur kilka „typów obiektów” poprzez dodanie specjalnych metod Pythona do owych struktur. Będą one użyte do obsługi iteracji, porównywania i reprezentacji łańcuchów. Nasz przykład jest oparty na strukturze struct servent, zwracanej przez getservbyname, getservbyport i getservent.
Tworzenie bazowego pliku interfejsowego SWIG
Najpierw tworzymy minimalny plik interfejsowy SWIG, zwany servtyp.i. Układ tego pliku interfejsowego jest podobny do układu pliku sysinfo.i:
%module servtyp
%{
#include <string.h>
#include <errno.h>
#include <netdb.h>
%}
Rozpoczynamy plik interfejsowy podaniem nazwy modułu za pomocą dyrektywy %module servtyp. Następnie, używając dyrektyw %{ lub %}, dorzucamy kilka instrukcji dołączania do pliku owijającego:
%include "exception.i"
%include "typemaps.i"
Uwzględniamy obsługę wyjątków i odwzorowania typów za pomocą dyrektywy %include:
%typemap(python,except) struct servent *, char *, PyObject *,int {
$function
if(PyErr_Occurred()) {
$cleanup
return NULL;
}
}
W pliku servtyp. i tworzymy jedną obsługę wyjątku, która poprzez funkcje: struct servent *, char *, PyObject * i int, obsługuje cztery różne zwracane typy. Pojedyncza obsługa wyjątków może obsługiwać wszystkie cztery typy funkcji, ponieważ wyraźnie sprawdza ona zgłoszenie wyjątku, używając funkcji PyErr_Occured. Pozostała część pliku servtyp.i jest następująca:
struct servent {
%addmethods {
servent(PyObject *name_or_port,char *proto="tcp") {
struct servent *res = NULL;
if(PyInt_Check(name_or_port))
res = getservbyport(ntohs(PyInt_AsLong(name_or_port)),
proto);
else if(PyString_Check(name_or_port))
res = getservbyname(PyString_AsString(name_or_port),
proto);
else {
SWIG_exception(SWIG_ValueError,"Invalid name or port\ type");
}
if(NULL == res) {
SWIG_exception(SWIG_RuntimeError,strerror(errno));
}
return res;
} /* end servent constructor /
~servent() { } / do nothing in destructor */
PyObject *__str__() {
char info[128];
sprintf(info,"servent %s port %d proto %s",
self->s_name,ntohs(self->s_port),self->s_proto);
return PyString_FromString(info);
}
} /* end addmethods */
%readonly
%name(name) char *s_name;
%name(aliases) struct servent_alias *s_aliases;
%name(aliases) char **s_aliases;
%name(port) int s_port;
%name(proto) char *s_proto;
%readwrite
}
W końcu, definiujemy strukturę servent i trzy dodatkowe metody:
konstruktor servent
destruktor ~servent
specjalną metodę w języku Python __str__
W tym przykładzie umieściliśmy dyrektywę %addmethods przed podaniem atrybutów metody (s_name, s_aliases, itd.). Dzięki temu, umożliwiliśmy zdefiniowanym przy pomocy %addmethods metodom, zastąpienie wszystkich metod, wygenerowanych automatycznie przez SWIG, przy przetwarzaniu atrybutów struktury. Omówimy ten warunek w dalszej części tego rozdziału.
Zamiast pozwolić na bezpośrednie przetworzenie netdb.h przez SWIG, określiliśmy ręcznie atrybuty struktury servent poprzez staranną replikację układu prawdziwej struktury servent, podanej w netdb.h. Dokonaliśmy tego z dwóch powodów. Po pierwsze, jest znacznie łatwiej używać dyrektywy SWIG %readonly do zaznaczania atrybutów, przeznaczonych tylko do odczytu. Po drugie, użyliśmy dyrektywy %name dla nadania atrybutom ładniejszych nazw — przykładowo %name(port) int s_port pozwala na odwołanie się do tego atrybutu z Pythona jako port, pomijając wiodące 's_'.
Poświęcimy teraz trochę miejsca na przeanalizowanie trzech metod, które dodaliśmy.
Pierwsza, konstruktor servent, jest użyta do utworzenia kopii obiektu servent. Konstruktor przyjmuje dwa argumenty:
typu PyObject *, który nazwaliśmy zastępczo name_or_port,
oraz typu char *, nazwanego proto, który przyjmuje domyślnie wartość łańcuchową "tcp".
SWIG udostępnia obsługę argumentów domyślnych . Nie jest to część interfejsu programowania C API Pythona.
W konstruktorze servent sprawdzamy, czy name_or_port jest liczbą całkowitą, korzystając z funkcji PyInt_Check, z interfejsu programowania C API Pythona. Funkcja zwraca wartość logiczną prawda, jeśli przekazany do funkcji argument PyObject jest liczbą całkowitą lub obsługuje specjalną metodę w Pythonie __int__.
Jeśli name_or_port jest liczbą całkowitą, używamy getservbyport, aby uzyskać strukturę servent dla podanego portu.
Jeśli name_or_port jest łańcuchem, używamy getservbyname, aby uzyskać strukturę servent dla podanej nazwy.
Jeśli name_or_port nie jest liczbą całkowitą ani łańcuchem, to zgłaszamy wyjątek typu SWIG_ValueError.
Wreszcie, jeśli struktury servent nie znaleziono, to wtedy NULL == res przyjmie wartość logiczną prawda i jest zgłoszony wyjątek SWIG_RuntimeError.
Należy zwrócić uwagę, że w konstruktorze używamy SWIG_exception, makrodefinicji która wywołuje funkcję _SWIG_exception, a następnie zwraca NULL. Trzeba uważać, aby wybrać odpowiednio do sytuacji makrodefinicję lub funkcję. Makrodefinicja SWIG_exception nie zawiera swoich własnych nawiasów klamrowych C {}, a zatem musi być ostatnią instrukcją w bloku kodu lub być ujęta w nawiasy klamrowe dla zapewnienia właściwego powrotu funkcji.
Jednakże jest pewien problem związany z konstruktorem servent, pokazanym powyżej. Funkcje getserbyport i getservbyname zwracają wskaźnik do struktury statycznej. Jeśli dwa obiekty servent są utworzone, to oba będą odnosić się do tych samych danych — nawet jeśli ten drugi określa inny port.
SWIG zakłada, że konstruktory zwrócą nowy obiekt, który musi być uwolniony przy usunięciu — ale my zwracamy wskaźnik do tego samego obiektu statycznego, którego nie wolno uwolnić. Zatem druga metoda, którą dodaliśmy jest destruktorem ~servent. Jej zawartość jest pusta, tak więc w rzeczywistości metoda ta nie robi nic. Ale poprzez wyraźne zdefiniowanie destruktora, zastąpiliśmy domyślny destruktor SWIG, który wywołałby free dla struktury servent.
Niestety, nadal możemy mieć w danej chwili tylko jeden obiekt servent. Utworzenie drugiej struktury servent zmieniłoby zawartość współużytkowanej struktury statycznej, zmieniając przy tym wartości, wskazywane przez uprzednio utworzoną strukturę servent. To problem, którego rozwiązania poszukamy nieco dalej.
Trzecia metoda dodana do struct servent to specjalna metoda w Pythonie __str__. Zwraca ona reprezentację łańcuchową (do odczytania przez człowieka) danego obiektu. W przykładzie ze strukturą servent, będzie zwrócony łańcuch Pythona, który podaje port, protokół i nazwę obiektu servent. Należy zwrócić uwagę w tej metodzie, na użycie ntohs do zamiany s_port z porządku sieciowego ( ang. network order) na porządek hosta (ang. host order), którego dokonano dla dobra prezentacji.
Przetwarzanie, kompilacja i testowanie pliku interfejsowego
Jak dla sysinfo.i przetwarzamy plik interfejsowy servtyp.i za pomocą następującego polecenia:
$ swig -python -make_default -shadow servtyp.i
Generating wrappers for Python
Następnie tworzymy nowy plik Makefile, używając poprzednio opisanej procedury uruchamiania „boot”. Plik Setup.in zawiera poniższe dwa wiersze:
*shared*
servtypc servtyp_wrap.c
Wykonanie polecenia make powoduje pojawienie się następującego wydruku:
$ make
gcc -fPIC -g -O2 -I/usr/include/python1.5 -I/usr/include/python1.5 -DHAVE_CONFIG_H -c ./servtyp_wrap.c
gcc -shared servtyp_wrap.o -o servtypcmodule.so
$
Teraz moduł jest już gotowy do testowania:
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import servtyp
>>> i = servtyp.servent(25)
>>> str(i)
'servent smtp port 25 proto tcp'
Importujemy moduł rozszerzenia servtyp, a następnie wytwarzamy kopię obiektu servent, wywołując konstruktor z pojedynczym argumentem '25'. Protokół nie jest podany — przyjmuje wartość domyślną tcp, gdyż to jest wartość domyślna dla argumentu proto. Następnie wywołujemy specjalną metodę __str__, stosując funkcję str dla kopii obiektu servent.
Możemy sprawdzić zdolność do obsługi wyjątku, podając nieobsługiwany port lub protokół przy wywołaniu konstruktora servent, w sposób podobny do poniższego:
>>> b = servtyp.servent(999,'udp')
Traceback (innermost last):
File "<stdin>", line 1, in ?
File "servtyp.py", line 46, in __init__
self.this = apply(servtypc.new_servent, (arg0,)+args)
RuntimeError: No such file or directory
>>>
Ponieważ port 999 nie odpowiada żadnej znanej usłudze, funkcja getservbyport zwraca NULL. Funkcja strerror przekształca errno na łańcuch "No such file or directory" (Nie ma takiego pliku ani katalogu), który jest potem zwrócony przez makrodefinicję SWIG_exception.
Ponieważ użyliśmy dyrektywy SWIG %name dla nadania atrybutom servent ładniejszych nazw, możemy uzyskać dostęp do tych atrybutów w następujący sposób:
>>> print i.proto, i.port, i.name
tcp 6400 smtp
>>>
Następujące po sobie instrukcje print drukują protokół, numer portu i nazwę usługi. Towarzysząca klasa (ang. shadow class) servent realizuje bezpośredni dostęp do atrybutu s_port. Ponieważ s_port jest przechowywany w porządku sieciowym, to pokazuje się jako 6400, a nie 25 (ten przykład został opracowany na komputerze o architekturze Intela).
Zastępowanie funkcji dostępu do atrybutów
Można usunąć problem porządku sieciowego, zastępując domyślne funkcji SWIG dostępu do atrybutów. SWIG automatycznie tworzy funkcje get do pobrania i put do nadania wartości atrybutów struktury. Do zdefiniowania własnej funkcję get dla atrybutu port można użyć dyrektywy %addmethod.
Nowa metoda zostaje dodana do struktury servent w pliku servtyp.i w następujący sposób:
} /* end servent constructor */
~servent() { } /* do nothing */
int port_get() {
return ntohs(self->s_port);
}
PyObject *_str_() {
Przetwarzając uaktualniony plik servtyp.i za pomocą SWIG otrzymujemy następujący wydruk:
$ swig -python -docstring -make_default -shadow servtyp.i
Generating wrappers for Python
servtyp.i : Line 54. Variable servent_port_get multiply defined (2nd definition ignored).
Należy zauważyć, że SWIG zgłasza ostrzeżenie w wierszu 54 (%name(port) int s_port;), że servent_port_get jest wielokrotnie zdefiniowane — funkcja ta została już zdefiniowana przez metodę port_get. Nasza własna funkcja port_get jest zdefiniowana wcześniej, niż domyślna funkcja SWIG jest generowana. Zatem SWIG zgłasza, że funkcja jest już zdefiniowana oraz pomija drugą (domyślną) definicję.
Atrybut port zwraca teraz oczekiwaną wartość:
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import servtyp
>>> i = servtyp.servent(25)
>>> print i.port
25
Tworzenie obiektu iteratora
Jednym z atrybutów struktury servent jest s_aliases. Jest to atrybut typu char **, który wskazuje na zakończoną znakiem zero tablicę łańcuchów. Kiedy realizujemy dostęp do tego atrybutu z Pythona, to otrzymujemy obiekt wskaźnika (ang. pointer object) zamiast wykazu nazw zastępczych:
>>> print i.aliases
_80e7824_char_pp
W rzeczywistości wolelibyśmy uzyskać wykaz nazw zastępczych lub obiekt, który może być użyty z operatorem Pythona for. W tym podrozdziale utworzymy nowy typ obiektu, obiekt iteratora (ang. iterator object), który będzie obsługiwał operator for. Obiekty iteratora są łatwe do utworzenia — są to zwyczajnie obiekty, które obsługują specjalną metodę Pytona __getitem__.
Metoda __getitem__ użyta z operatorem for otrzyma wzrastający numer indeksowy rozpoczynający się od zera. Obiekt iteratora potrzebuje jedynie zwrócić wartość, która odpowiada indeksowi, oraz zgłosić wyjątek IndexError, kiedy nie ma więcej wartości do zwrócenia.
W celu utworzenia obiektu iteratora, zdefiniujemy w servtyp.i nową strukturę, zwaną servent_alias. Nie powinna mieć żadnych atrybutów dostępnych z Pythona poza specjalną metodą __getitem__. W celu ukrycia innych atrybutów, struktura servent_alias będzie zdefiniowana dwa razy w pliku interfejsowym servtyp.i:
Pierwsza definicja będzie w sekcji otoczki — to znaczy, definicja będzie wpisana do servtyp_wrap.c bez interpretacji przez SWIG. Będzie zawierać prawdziwe atrybuty, wymagane przez strukturę servent_alias.
Druga definicja servent_alias będzie interpretowana przez SWIG. Będzie zawierać tylko metodę specjalną __getitem__ oraz żadnej definicji atrybutu.
Chociaż SWIG nie może zobaczyć żadnych definicji atrybutów, plik owijający servtyp_wrap.c będzie zawierał pierwszą definicję z właściwymi definicjami atrybutów. Z drugiej strony, nasza metoda __getitem__ faktycznie widzi owe atrybuty, ponieważ kod w C jest także bezpośrednio umieszczony w pliku owijającym.
Rozpoczynamy dodając do pliku interfejsowego servtyp.i pierwszą definicję servent_alias:
%module servtyp
%{
#include <string.h>
#include <errno.h>
#include <netdb.h>
struct servent_alias {
char **aliases;
};
%}
Struktura servent_alias posiada tylko jeden atrybut: char **aliases. Ustawimy ten atrybut tak, aby przyjął wartość s_aliases w strukturze servent. Ponieważ definicja tej struktury jest zawarta w obrębie dyrektyw SWIG %{ i }%, będzie ona wpisana bez interpretacji bezpośrednio do pliku owijającego.
Teraz dodajemy do pliku interfejsowego servtyp.i drugą definicję struktury servent_alias. Definicja ta jest przetworzona przez SWIG:
if(PyErr_Occurred()) {
$cleanup
return NULL;
}
}
struct servent_alias {
%addmethods {
char *__getitem__(int index) {
char **i = self->aliases;
while(i && *i) {
if(0 == index)
return *i;
index--;
i++;
}
SWIG_exception(SWIG_IndexError,"index out of bounds");
}
} /* end addmethods */
}
struct servent {
%addmethods {
Metoda specjalna __getitem__ struktury servent_alias akceptuje liczbę całkowitą jako index. Używa wartości index i zmiennej aliases dla określenia, którą nazwę zastępczą łańcucha zwrócić. Zgłasza wyjątek IndexError, jeśli index jest poza zakresem.
Na koniec, należy zmodyfikować strukturę servent tak, aby zwrócić kopię servent_alias. Rozumując logicznie, należy utworzyć funkcję dostępu aliases_get, która zastąpi domyślną funkcję dostępu SWIG. Wygląda to tak:
int port_get() {
return ntohs(self->s_port);
}
%new struct servent_alias *aliases_get() {
struct servent_alias *i = malloc(sizeof(*i));
i->aliases = self->s_aliases;
return i;
}
PyObject *__str__() {
Funkcja aliases_get jest zadeklarowana tak, aby zwrócić wartość typu struct servent_alias *. Należy zwrócić uwagę na użycie dyrektywy SWIG %new. Chociaż SWIG wie, że konstruktory zwracają pamięć zaalokowaną, to nie może wiedzieć, że ta funkcja zwraca nowy obiekt, który musi być uwolniony przy usunięciu. Dyrektywa %new informuje SWIG o zwróceniu przez funkcję nowego obiektu.
Na koniec, zmieniamy definicję atrybutu s_aliases tak, aby zdefiniować ją, jako wartość struct servent_alias *:
%name(name) char *s_name;
%name(aliases) struct servent_alias *s_aliases;
%name(port) int s_port;
Teraz można już przetestować obiekt iteratora. Utworzymy kopię obiektu servent dla protokołu discard:
>>> import servtyp
>>> i = servtyp.servent('discard')
Teraz tworzymy odsyłacz dla atrybutu aliases kopii obiektu servent aby uzyskać kopię struktury servent_alias:
>>> print i.aliases
'_80f67f8_struct_servent_alias_p'
I tu niemiła niespodzianka... To nie to, czego oczekiwaliśmy — otrzymaliśmy kopię struktury servent_alias, ale nie jest ona owinięta w odpowiednią dla niej klasę towarzyszącą (ang. shadow class) Pythona. Nie ma zatem sposobu, aby operator Pythona for mógł wywołać metodę specjalną __getitem__ dla tej kopii struktury.
Można zrozumieć powody wystąpienia tego problemu, analizując plik towarzyszący servtyp.py:
class serventPtr :
def aliases_get(self):
""""""
val = servtypc.servent_aliases_get(self.this)
val = servent_aliasPtr(val)
val.thisown = 1
return val
def __getattr__(self,name):
if name == "aliases" :
return servtypc.servent_aliases_get(self.this)
Ten zmodyfikowany przykład pokazuje, że w czasie uzyskiwania wartości aliases ze struktury servent za pomocą metody getattr, określona w pliku owijającym funkcja servent_aliases_get jest wywołana bezpośrednio. Mimo, że funkcja aliases_get wykonuje to samo wywołanie funkcji owijającej, to przed zwróceniem otrzymanej wartości zostaje jeszcze objęta kapsułką kopii servent_aliasPtr.
Można teraz porównać wartość nazwy zastępczej otrzymanej powyżej z wartością zwróconą przez aliases_get:
>>> print i.aliases_get()
<C servent_alias instance>
Pokazuje to poważne ograniczenie w wersji SWIG, użytej do utworzenia tych przykładów. Chociaż SWIG poprawnie zachowuje się wobec funkcji aliases_get i wie, że aliases_get zastępuje domyślną funkcję owijającą dla atrybutu aliases, to jednak nie dokonuje właściwego dostrojenia funkcji owijającej, poprzez objęcie wartości atrybutu w kapsułkę kopii servent_aliasPtr.
Używana przez Czytelnika wersja SWIG być może nie posiada tego ograniczenia. My tymczasem, zamiast realizować dostęp do atrybutu aliases, użyjemy bezpośrednio funkcji aliases_get.
Kontynuując testowanie struktury servent_alias, można wywołać specjalną metodę Pythona __getitem__ używając wzrastającej wartości indeksu:
>>> print i.aliases_get()[0]
'sink'
>>> print i.aliases_get()[1]
'null'
>>> print i.aliases_get()[2]
Traceback (innermost last):
File "<stdin>", line 1, in ?
File "servtyp.py", line 10, in __getitem__
val = servtypc.servent_alias__getitem__(self.this,arg0)
IndexError: index out of bounds
>>>
Metoda specjalna servent_alias__getitem__ zgłasza wyjątek typu IndexError, kiedy wartość indeksu jest poza zakresem. Kopia servent_aliases może być użyta wraz z operatorem Pythona for w sposób następujący:
>>> for alias in i.aliases_get():
... print alias
...
sink
null
>>>
Korekta problemu pojedynczej kopii
Jak to wzmiankowano, konstruktor servent zwraca tę samą kopię (ang. copy) struktury servent ponieważ biblioteka C zwraca wskaźnik do obiektu statycznego. Aby temu zaradzić, trzeba przekonać konstruktor servent, aby zwrócił jakąś kopię tej struktury, zamiast wskaźnika do pojedynczego statycznego egzemplarza dostarczonego przez bibliotekę C.
Można tego dokonać z pomocą funkcji owijającej, którą nazwiemy CopyServent. Jej zadanie polega na utworzeniu nowej dynamicznej kopii struktury servent, wraz z nazwami zastępczymi. Kod nie jest charakterystyczny dla Pythona, ale trzeba będzie poprawić jakość poniższych przykładów.
Należy zauważyć, że poniższy kod zawiera bardzo oszczędną kontrolę błędów i szacunkowe założenia, co do rozmiarów struktury. Kontrola błędów została pominięta ze względu na szczupłość miejsca oraz dla uproszczenia przykładu.
Należy zmienić plik servtyp.i wstawiając poniższy kod:
if(PyErr_Occurred()) {
$cleanup
return NULL;
}
}
%wrapper %{
#define MAX_MY_ALIASES 10
struct my_servent {
struct servent s;
char *aliases[MAX_MY_ALIASES];
char data[1024];
char name[255];
char proto[16];
};
struct servent *CopyServent(struct servent *res)
{
struct my_servent *copy;
char *cp,**icp;
int index;
copy = (struct my_servent *) calloc(1,sizeof(struct my_servent));
if(NULL == copy) {
SWIG_exception(SWIG_MemoryError,"out of memory");
}
memcpy(copy,res,sizeof(*res));
strcpy(copy->name,res->s_name);
copy->s.s_name = copy->name;
strcpy(copy->proto,res->s_proto);
copy->s.s_proto = copy->proto;
/* now fixup aliases */
index = 0;
copy->aliases[0] = NULL;
cp = copy->data;
copy->s.s_aliases = copy->aliases;
icp = res->s_aliases;
while(icp && *icp && index < MAX_MY_ALIASES &&
(cp - copy->data) < sizeof(copy->data)-255) {
copy->aliases[index++] = cp;
strcpy(cp,*icp);
cp = cp + strlen(cp)+1;
icp++;
}
copy->aliases[index] = NULL;
return (struct servent *) copy;
}
%}
struct servent_alias {
%addmethods {
Należy zwrócić uwagę na użycie dyrektywy SWIG %wrapper. Pod pewnymi względami dyrektywa ta jest podobna do dyrektyw %{ i }% — kod w niej zawarty jest wpisany do pliku owijającego bez interpretacji. Jednakże dyrektywa %wrapper jest głównie używana dla wstrzyknięcia dużych sekcji kodu w języku C do pliku owijającego, podczas gdy dyrektywy %{ i }% są zarezerwowane typowo dla dołączania plików nagłówkowych i deklaracji struktur.
Na koniec, należy zmienić konstruktor ervent i wyeliminować destruktor-atrapę ~servent, gdyż obecnie zwracamy nowe obiekty, które muszą być zwolnione przy usuwaniu.
if(NULL == res) {
SWIG_exception(SWIG_RuntimeError,strerror(errno));
}
return CopyServent(res);
} /* end servent constructor /
int port_get() {
return ntohs(self->s_port);
}
Obsługa porównania obiektów
W związku z tym, że zwracamy unikatowe kopie obiektów servent, możemy dodać obsługę dla metody specjalnej Pythona __cmp__. Metoda ta jest używana przez operatory porównań takie jak <, == oraz >.
Trzeba dodać poniższy kod do pliku interfejsowego servtyp.i, a następnie ponownie uruchomić SWIG, polecenie make oraz interpreter Pythona dla testowania:
%new struct servent_alias *aliases_get() {
struct servent_alias *i = malloc(sizeof(*i));
i->aliases = self->s_aliases;
return i;
}
$ python int __cmp__(PyObject *other_obj) {
/* compare to other object */
struct servent *other;
PyObject *temp_obj;
if(NULL != (temp_obj = PyObject_GetAttrString(other_obj,"this")))
other_obj = temp_obj;
else
PyErr_Clear();
if(!PyString_Check(other_obj) ||
SWIG_GetPtr(PyString_AsString(other_obj),
(void **) &other, "_struct_servent_p")) {
_SWIG_exception(SWIG_TypeError,
"other object must be type servent");
return 0;
}
if(self->s_port < other->s_port)
return -1;
else if(self->s_port == other->s_port)
return strcasecmp(self->s_proto,other->s_proto);
else
return 1;
} /* end __cmp__ */
PyObject *__str__() {
Python rozpoczyna wywołując metodę specjalną __cmp__ dla wykonania porównania. Ta implementacja __cmp__ przeprowadza porównania tylko z innymi kopiami servent.
Pierwszy krok w operacji porównania to pobranie wskaźnika do drugiej struktury servent. Naszą funkcję porównania można porównać z kopią klasy towarzyszącej struktury servent, albo też z kopia niskiego poziomu w języku C struktury servent.
W pierwszym przypadku, other_obj będzie obiektem Pythona, który ma atrybut nazwany this. Atrybut this będzie łańcuchem, który określa nazwę kopii niskiego poziomu w języku C drugiej struktury.
Używamy funkcji interfejsu C API w Pythonie PyObject_GetAttrString, aby otrzymać wartość atrybutu this obiektu other_obj. Jeśli nie ma takowego, to PyObject_GetAttrString zgłosi wyjątek. Wyjątek usuwamy, wywołując PyErr_Clear. Jeśli obiekt other_obj nie zawierał atrybutu this, to uaktualniamy other_obj tak, aby wskazywał do wartości tego atrybutu.
Następnie sprawdzamy czy other_obj jest łańcuchem. Jeśli tak, to wywołujemy funkcję SWIG_GetPtr aby zamienić ten łańcuch na wskaźnik typu _struct_servent_p. W przeciwnym razie, zgłaszamy wyjątek typu TypeError, zapowiadający, że drugi obiekt musi być typu servent ("other object must be type servent").
Skąd wiedzieliśmy, że SWIG_GetPtr powinien być użyty w ten sposób? Skąd wiedzieliśmy, że struktura servent jest zwana _struct_servent_p? Otóż odpowiedź na te i inne pytania można znaleźć studiując kod C zawarty w pliku servtyp_wrap.c. SWIG zawiera komentarze opisujące sposób użycia SWIG_GetPtr, a także można zobaczyć, jak SWIG implementuje funkcje metod. Większość kodu dla __cmp__ jest wzięta bezpośrednio z pliku servtyp_wrap.c.
Po uzyskaniu wskaźnika do drugiej struktury servent, można dokonać porównania obu tych struktur i zwrócić odpowiednią wartość dla potrzeb sortowania. W tym przykładzie, sortujemy w oparciu o numer portu, a następnie nazwę protokołu.
Metoda specjalna __cmp__ może być przetestowana w sposób następujący:
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import servtyp
>>> i = servtyp.servent('smtp')
>>> b = servtyp.servent(9)
>>> print i == b
0
>>> print i < b
0
>>> print i > b
1
>>> print str(i), " ? ", str(b)
servent smtp port 25 proto tcp ? servent discard port 9 proto tcp
Obsługa indeksowania obiektów (object hashing)
Obiekt w Pythonie, który udostępnia metodę specjalną __hash__ może być użyty jako klucz w słowniku. Struktura servent nie jest zbyt użyteczna jako klucz w słowniku, ale w tym podrozdziale zostanie pokazane, w jaki sposób zaimplementować metodę specjalną __hash__ przy pomocy SWIG.
Podobnie jak dla metody specjalnej __cmp__, używamy dyrektywy %addmethods dla dodania __hash__ do struktury servent w pliku interfejsowym servtyp.i. Odpowiednia modyfikacja tego pliku wygląda następująco:
return 1;
} /* end __cmp__ */
int __hash__() {
unsigned long v = self->s_port;
if(!strcasecmp(self->s_proto,"udp"))
v = v + 0x100000;
return v;
} /* end __hash__ */
PyObject *__str__() {
Metoda specjalna __hash__ musi zwrócić tę samą wartość numeryczną dla tego „samego” obiektu. W tym przykładzie, zakładamy, że funkcja mieszająca (ang. hash function) jest oparta wyłącznie na wartości servent s_port i s_proto. Wiersz 'v = v + 0x100000' jest używany do wygenerowania unikatowej wartości w sytuacji, kiedy numer portu jest taki sam, ale protokoły są różne.
Po przebudowaniu modułu rozszerzenia, można przetestować go w Pythonie w sposób następujący:
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import servtyp
>>> i = servtyp.servent('smtp')
>>> b = servtyp.servent(22)
>>> testDict = {}
>>> testDict[i] = "email"
>>> testDict[b] = "ssh"
>>> for v in testDict.keys(): print str(v)," ==> ",testDict[v]
...
servent ssh port 22 proto tcp ==> ssh
servent smtp port 25 proto tcp ==> email
>>
>>> print testDict[i]
'email'
Użycie destruktora obiektu do robienia porządków
Nasz ostatni przykład z użyciem SWIG pokazuje, jak użyć destruktora obiektu do zrobienia porządków z efektami ubocznymi, takimi jak zamknięcie otwartego pliku lub iteracja biblioteki C. W tym przykładzie utworzymy inny obiekt iteratora. Obejmie on w kapsułkę funkcje setservent, getservent i endservent.
Utworzymy nowy obiekt iteratora, zwany ServentIterator. Zwróci on obiekty servent ze swojej metody specjalnej __getitem__. Są to końcowe zmiany wprowadzone w pliku interfejsowym servtyp.i:
struct servent_alias {
char **aliases;
};
struct ServentIterator {
int isopen;
};
%}
Najpierw dodajemy definicję w C dla struktury ServentIterator w sekcji otoczki pliku interfejsowego. Struktura ServentIterator ma jeden atrybut — isopen, który jest niezerowy, jeśli setservent został wywołany.
Pod koniec pliku interfejsowego servent.i dodajemy następującą definicję dla struktury ServentIterator:
struct ServentIterator {
%addmethods {
%new struct servent *__getitem__(int index) {
struct servent *res;
if(!self->isopen) {
self->isopen++;
setservent(1);
}
res = getservent();
if(NULL == res) {
endservent();
self->isopen = 0;
SWIG_exception(SWIG_IndexError,"no more entries");
}
return CopyServent(res);
} /* end __getitem__ */
~ServentIterator() {
if(self->isopen)
endservent();
free(self);
}
} /* end addmethods */
};
Metoda specjalna __getitem__ zwraca kolejną strukturę servent, zwróconą przez funkcję getservent. Jeśli nie ma więcej dostępnych wpisów, to __getitem__ zgłasza wyjątek IndexError. Należy zwrócić uwagę na użycie dyrektywy SWIG %new. Informuje ona SWIG, o zwróceniu przez funkcję nowego obiektu, który musi być uwolniony przy usuwaniu.
Używamy także poprzednio zdefiniowanej funkcji pomocniczej CopyServent dla wytworzenia unikatowej kopii struktury servent zwróconej przez funkcję getservent.
Przy pierwszym wywołaniu __getitem__, atrybut isopen struktury ServerIterator będzie miał wartość 0 (logiczny fałsz), ponieważ calloc jest użyty do zaalokowania pamięci dla struktury ServerIterator. Ustawiamy wartość atrybutu na 1 (logiczna prawda) i wywołujemy setservent, aby rozpocząć proces iteratora.
Destruktor ~ServerIterator sprawdza atrybut isopen dla ustalenia, czy funkcja endservent może być wywołana. Zwalnia także pamięć zaalokowaną dla struktury ServerIterator.
Obiekt ServerIterator można przetestować przy pomocy operatora Pythona for, drukując wykaz wszystkich usług zawartych w /etc/services:
>>> for i in servtyp.ServentIterator():
... print str(i)
... for a in i.aliases_get(): print )," ",a
...
servent tcpmux port 1 proto tcp
servent echo port 7 proto tcp
servent echo port 7 proto udp
servent discard port 9 proto tcp
sink
null
servent discard port 9 proto udp
sink
null
Biblioteka C zakłada, że tylko jedno zadanie będzie używać wywołań bibliotecznych getservent w danej chwili, tak więc w rzeczywistości nie jest bezpiecznie mieć więcej niż jeden aktywny ServentIterator w danej chwili. Jest to zatem przykład o niezbyt dużym znaczeniu praktycznym, niemniej jednak służy jako praktyczna demonstracja technik rozszerzania.
Zakończyliśmy nasz przegląd użycia SWIG do owinięcia funkcji C dla potrzeb Pythona. Trzeba pamiętać, że SWIG w ograniczonym zakresie obsługuje funkcje C++ wraz z Pythonem. SWIG zawiera dużą ilość dokumentacji, która szerzej wyjaśnia jak korzystać z cech, które zostały tu omówione.
Należy też pamiętać, że istnieje wiele innych sposobów na to, aby dojść do naszych wyników i wniosków. Warto eksperymentować ze SWIG dla znalezienia tych sposobów, najlepiej odpowiadających danemu zastosowaniu.
Rozszerzanie Pythona przy użyciu C API
Jak to już widzieliśmy, SWIG w istotny sposób ułatwia zadanie budowania dla Pythona modułów rozszerzenia w C— choć niestety wytworzony kod C nie należy do najbardziej wydajnych. Nowa druga wersja SWIG pozwala mieć nadzieję, że będzie produkowany wydajniejszy kod. Niemniej jednak niektóre aplikacje nadal będzie lepiej zbudować ręcznie, pisząc niezbędne funkcje owijające samodzielnie, niż polegać na zautomatyzowanych wytworach SWIG. Co więcej, interfejs programowania C API Pythona pozwala modułom rozszerzenia na udostępnienie zakresu możliwości i interfejsów obiektów znacznie przewyższających te oferowane przez sam SWIG.
Interfejs programowania C API Pythona udostępnia wszelki zakres możliwości niezbędnych do opracowania modułów rozszerzających w C lub C++. Wszak SWIG używa tego samego API kiedy generuje pliki owijające. Często można poznać sposób użycia pewnych funkcji lub strukturę modułu rozszerzenia poprzez badanie kodu w pliku owijającym utworzonym przez SWIG.
Typy obiektów Pythona
Język Python obsługuje różnorodne typy obiektów, w tym łańcuchy, liczby całkowite, liczby zmiennoprzecinkowe (ang. floats), listy, wielokrotki (ang. tuples), słowniki, moduły, klasy i kopie (ang. instances).Dla każdego z tych typów jest odpowiedni interfejs C API. Dokumentacja Pythona dostarcza pełnej informacji o tych funkcjach.
Podczas pracy z prostymi typami danych, takimi jak łańcuchy, liczby całkowite, długie liczby całkowite (ang. longs) i liczby zmiennoprzecinkowe, mamy dla każdego typu do dyspozycji trzy podstawowe funkcje:
testowanie obiektu Pythona ze względu na kompatybilność
wydobycie wartości zapisanej w języku C z danych zapisanych w języku Python
wydobycie wartości zapisanej w języku Python z danych zapisanych w języku C
Dla przykładu, te podstawowe typy obiektów mają podobne funkcje nimi zarządzające:
Typ danych |
Kontrola |
Z Pythona do języka C |
Z języka C do Pythona |
string |
PyString_Check |
PyString_AsString |
PyString_FromString |
int |
PyInt_Check |
PyInt_AsInt |
PyInt_FromInt |
long |
PyLong_Check |
PyLong_AsLong |
PyLong_FromLong |
float |
PyFloat_Check |
PyFloat_AsDouble |
PyFloat_FromDouble |
Interfejs C API traktuje wszystkie obiekty Pythona jako dosłownie typu PyObject. To znaczy, że będziemy manipulować obiektami Pythona tak jak wskaźnikami do obiektów typu PyObject. Na przykład, aby utworzyć obiekt łańcucha w Pythonie z łańcucha w języku C, użylibyśmy następującego kodu:
PyObject *pystring = PyString_FromString("probny lancuch");
Podobnie, aby przekształcić obiekt Pythona na łańcuch w języku C, można użyć poniższego kodu:
if (PyString_Check(pystring)) {
char *mystring = PyString_AsString(pystring);
}
Funkcja PyString_AsString wywołuje również PyString_Check. Można nieco poprawić wydajność używając PyString_AS_STRING zamiast PyString_AsString, ale to powinno się robić jedynie wtedy, gdy ma się pewność, że przekazany obiekt jest łańcuchem (lub typem łańcucha), ponieważ PyString_AS_STRING pomija wywołanie PyString_Check.
Podobne funkcje istnieją dla liczb całkowitych i zmiennoprzecinkowych.
Zliczanie odsyłaczy oraz prawo własności
Python zarządza pamięcią poprzez zliczanie odsyłaczy (ang. counting references) do każdego zaalokowanego obiektu. Zamiast używać kolekcji informacji bez sensu (jak w Javie) lub wyraźnych funkcji alloc-free (jak w języku C), Python śledzi jak wiele odsyłaczy jest przechowywanych dla danego obiektu. Kiedy operator del jest zastosowany do obiektu Pythona, sam obiekt nie jest bezpośrednio zwolniony — zamiast tego zliczenie odsyłaczy (ang. reference count) jest zmniejszone o jeden. Jeśli zliczenie odsyłaczy spada do zera to wtedy obiekt jest zwolniony.
Na przykład funkcja PyString_FromString zwraca obiekt Pythona, dla którego zliczenie odsyłaczy jest ustawiony na jeden. Ten obiekt może być bezpośrednio zwrócony do Pythona z funkcji w języku C. Jednakże, jeśli obiekt nie jest już więcej potrzebny to trzeba jawnie zmniejszyć o jedną jednostkę zliczenie odsyłaczy obiektów przy pomocy funkcji Py_DECREF.
Są trzy typy odsyłaczy: własnościowe, pożyczone lub skradzione.
Odsyłacz jest własnościowy (ang. owned), kiedy został utworzony przez funkcję C z interfejsu programowania aplikacji (C API), taką jak PyString_FromString.
Na przykład można przekazać stan posiadania obiektu poprzez zwrócenie obiektu z funkcji C wywołanej z Pythona. Zasadniczo, dowolna funkcja z interfejsu C API Pythona, która tworzy obiekt, zwróci odsyłacz własnościowy.
Odsyłacz pożyczony (ang. borrowed) jest odsyłaczem do obiektu, który nie jest własnościowy. Ponieważ nie posiadamy odsyłacza to nie możemy go przekazać. Wywołanie funkcji C API może spowodować usunięcie obiektu, a zatem uczynić niepoprawnym uchwyt dla obiektu.
Pożyczonymi odsyłaczami należy zarządzać ostrożnie. Na przykład, funkcja PyDict_GetItem z interfejsu C API zwraca odsyłacz pożyczony do obiektu. Można nabyć prawo własności pożyczonego odsyłacza przy użyciu funkcji Py_INCREF. Jednakże należy pamiętać, aby zrezygnować ze stanu posiadania przy pomocy funkcji Py_DECREF, gdy już operacje na obiekcie są zakończone.
Wreszcie, odsyłacz skradziony (ang. stolen) to taki, który został nam zabrany przez funkcję C API. Na przykład funkcje PyList_SetItem i PyTuple_SetItem przyjmują prawo własności odsyłaczy im przesłanych. Są tylko dwie takie funkcje, które to czynią. Są one powszechnie używane do tworzenia list i wielokrotek (ang. tuple) obiektów, skonstruowanych przy użyciu funkcji C API, które zwracają własnościowe odsyłacze.
Należy starannie sprawdzić dokumentację Pythona, aby mieć pewność, który odsyłacz do obiektu otrzymuje się z funkcji C API oraz jakie rodzaj odsyłacza do obiektu jest oczekiwany przez wywoływaną funkcję.
Jest bardzo ważne aby ostrożnie zarządzać prawem własności i zliczeniem odsyłaczy. Dowolny obiekt nigdy nie zostanie zwolniony jeśli jemu odpowiadający odsyłacz istnieje i jest własnościowy (posiadany). Jeśli ma się wiele owych „posiadanych i nieużywanych” obiektów, to marnuje się sporo pamięci. W dodatku, obiekty których zliczenieodsyłaczy jest zmniejszone poniżej zera mogą spowodować błąd ochrony i niepowodzenie programu.
Większość funkcji C API zwraca odsyłacze własnościowe. Jednakże funkcje takie jak PyTuple_GetItem, PyList_GetItem, PyDict_GetItem i PyDict_GetItemString zwracają bez wyjątku odsyłacze pożyczone. Co więcej, PyList_SetItem i PyTuple_SetItem w istocie kradną własnościowe odsyłacze do obiektów, które im przekazujemy. Zatem musimy posiadać dowolny odsyłacz przekazywany do funkcji. Jeśli wywołamy te dwie funkcje z pożyczonym odsyłaczem, to będziemy mieli poważne kłopoty.
Należy pamiętać, że dowolne wywołanie C API może sprawić, że pożyczony odsyłacz staje się niepoprawny. To tyczy się samej funkcji Py_DECREF, która często w istocie powoduje usunięcie obiektu — ten obiekt może z kolei usunąć inny obiekt, do którego posiadamy pożyczony odsyłacz.
Przegląd metod opracowywania modułów rozszerzenia w języku C
Tworzenie modułu rozszerzenia w języku C przy użyciu C API wiąże się z wykonaniem tych samych kroków co w przypadku użycia generatora SWIG. Należy utworzyć plik Setup.in i postępować według instrukcji zarysowanych w Makefile.pre.in tak, by zainicjować (ang. bootstrap) utworzenie pliku Makefile.
Po skopiowaniu pliku Makefile.pre.in do bieżącego katalogu (rozpoczniemy ten projekt w nowym i pustym katalogu), tworzymy plik Setup.in o następującej zawartości:
*shared*
capi capi.c
Następnie należy wykonać następującą procedurę ładowania:
$ make -f Makefile.pre.in boot
To tworzy plik Makefile, który zależy od pliku źródłowego C capi.c.
Struktura modułu rozszerzenia
W odróżnieniu od pliku interfejsowego dla SWIG, nie ma tu szczególnych wymagań co do formatu dla plików źródłowych C modułu rozszerzenia. Są jedynie cztery rzeczy, które muszą być zrobione:
włączyć Python.h, by uzyskać dostęp do C API;
skonstruować tablicę struktur PyMethodDef, opisujących każdą funkcję zdolną do wywołania Pythona;
wyeksportować pojedynczą funkcję nazwaną init<nazwa_modulu> (przykładowo, initcapi);
wywołać funkcję Py_InitModule z obrębu funkcji wyeksportowanej dla zainicjalizowania modułu.
Funkcja init
Utwórzmy prosty moduł rozszerzenia, który nie definiuje żadnej funkcji wywoływanej przez Python. Posłuży to jako punkt startowy dla naszych przykładów. Należy utworzyć plik zwany capi.c zawierający następujący tekst:
#include "Python.h"
static PyMethodDef capi_functions[] = {
{ NULL, NULL }
};
void
initcapi()
{
Py_InitModule("capi",capi_functions);
}
Plik capi.c importuje Python.h (należy zwrócić uwagę na dużą literę „P”). W obrębie tego pliku tworzymy tablicę struktur PyMethodDef — tablica jest zakończona definicją NULL. Wszystkie zmienne i funkcje są deklarowane jako statyczne, z wyjątkiem funkcji init.
Definicja typu PyMethodDef jest zdefiniowana przez methodobject.h, który jest włączony automatycznie przez Python.h.
Nazwa funkcja init bierze się od nazwy modułu — w tym przypadku initcapi. W obrębie tej funkcji wywołuje się Py_InitModule, przekazując jako argumenty nazwę modułu oraz tablicę struktur PyMethodDef.
Kompilację i test modułu przeprowadzamy następująco:
$ make
gcc -fPIC -g -O2 -I/usr/include/python1.5 -I/usr/include/python1.5 -DHAVE_CONFIG_H -c ./capi.c
gcc -shared capi.o -o capi.so
$ python
Python 1.5.2 (#1, Sep 17 1999, 20:15:36) [GCC egcs-2.91.66 19990314/Linux]
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import capi
>>> dir(capi)
['__doc__', '__file__', '__name__']
>>>
Należy zauważyć, że moduł capi posiada automatycznie łańcuchy doc, file i name przypisane przez Pythona. Poza tym nie ma zdefiniowanych funkcji w obrębie tego modułu. Nadeszła pora na zdefiniowanie pierwszej funkcji.
Proste funkcje
Rozpoczniemy od zdefiniowania prostej funkcji w języku C, dokonując następujących zmian w pliku capi.c:
#include <stdio.h>
#include "Python.h"
static PyObject *
PrintString(PyObject *self, PyObject *args)
{
const char *str;
if(!PyArg_ParseTuple(args,"s:PrintString",&str))
return NULL;
print("print: %s\n",str);
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef capi_functions[] = {
{ "PrintString", PrintString, METH_VARARGS},
{ NULL, NULL }
};
Zdefiniowaliśmy funkcję o nazwie PrintString. Python przekazuje dwa argumenty do funkcji C:
Pierwszy z nich, self, jest NULL chyba, że funkcja jest metodą kopii obiektu (więcej o tym poniżej).
Drugi, args, jest wielokrotką zawierającą wszystkie argumenty przekazane z Pythona.
Python udostępnia pożyteczną funkcję PyArg_ParseTuple dla przetwarzania argumentów funkcji. Funkcja ta pobiera wielokrotkę args, łańcuch szablonu i zero lub więcej zmiennych wyjściowych. W naszym przykładzie PyArg_ParseTuple oczekuje na pojedynczy obiekt łańcucha. Przekształci ten obiekt łańcucha na zmienną typu char * i zapisze ją w zmiennej str.
Należy zauważyć, że str jest stałą ponieważ adres char * zwrócony z PyArg_ParseTuple wskazuje na łańcuch, który nie powinien być modyfikowany.
Funkcja PyArg_ParseTuple zwraca wartość niezerową w razie powodzenia. Zwraca zero jeśli kończy się niepowodzeniem, zgłaszając przedtem wyjątek. Funkcje C muszą zwrócić NULL, kiedy został zgłoszony wyjątek. Większość funkcji C API Pythona także zwróci NULL, kiedy funkcja kończy się niepowodzeniem (uprzednio zgłaszając wyjątek). Jeśli otrzymamy NULL od funkcji C API Pythona, to wtedy musimy zrezygnować z prawa własności do wszystkich posiadanych odsyłaczy i zwrócić NULL z naszej funkcji. W taki sposób wyjątki są przekazane w górę łańcucha wywołań.
Jeśli funkcji PyArg_ParseTuple się powiedzie, to str wskaże do tekstu łańcucha. Wywołujemy printf, przekazując zmienną str.
Na koniec, zwracamy Py_None jako wartość zwróconą z funkcji PrintString. Py_None jest zdefiniowane w Python.h. Jest to obiekt identyfikacji (ang. identity object), mówiący Pythonowi, że „żadna wartość nie jest zwracana”. Zwrócenie Py_None odpowiada zadeklarowaniu funkcji w języku C jako typu void.
Warto zauważyć, że zwiększamy o jeden zliczenie odsyłaczy Py_None za pomocą Py_INCREF. To udziela nam prawa własności do odsyłacza, który następnie zwracamy do Pythona. Funkcje C, muszą albo zwracać NULL (po uprzednim zgłoszeniu wyjątku), albo zwracać odsyłacz własnościowy do obiektu Pythona.
Aby przetestować nowy moduł uruchamiamy make, ładujemy Pythona i importujemy moduł capi:
>>> import capi
>>> capi.PrintString("hej tam")
print: hej tam
>>> capi.PrintString(1)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: PrintString, argument 1: expected string, int found
>>> capi.PrintString("hej tam","czesc")
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: PrintString requires exactly 1 argument; 2 given
>>> ^D
Jest kilka interesujących rzeczy w tym przykładzie, na które należy zwrócić uwagę.
Kiedy przekazujemy pojedynczy łańcuch do funkcji PrintString, to zostaje on posłusznie wydrukowany. PyArg_ParseTuple spotyka niepowodzenie, jeśli przekazujemy do PrintString obiekt Pythona, który nie jest łańcuchem ani nie obsługuje metody specjalnej Pythona __str__. Zgłasza automatycznie wyjątek, który jest automatycznie wydrukowany jako część informacji śledzenia wstecznego z interpretera Pythona. Należy zauważyć, że słowo „PrintString” w informacji TypeError pochodzi z porcji „:PrintString” łańcucha formatującego przekazanego do PyArg_ParseTuple.
Można też zobaczyć co się stanie jak dwa argumenty zostaną przekazane do PrintString. Funkcja PyArg_ParseTuple jest bardzo wszechstronna.
Należy sięgnąć do dokumentacji Pythona dla uzyskania pełnej listy opcji formatowania, których można używać.
Odrobinę bardziej złożona funkcja
Zmodyfikujmy PrintString tak, by akceptowała albo liczbę całkowitą albo łańcuch. Wprowadzamy zatem następujące zmiany do capi.c:
PrintString(PyObject *self, PyObject *args)
{
PyObject *o;
if(!PyArg_ParseTuple(args,"O:PrintString",&o))
return NULL;
if(PyString_Check(o))
printf("print string: %s\n",PyString_AS_STRING(o));
else if(PyInt_Check(o))
printf("print int: %d\n",PyInt_AS_LONG(o));
else {
PyErr_SetString(PyExc_TypeError,
"invalid type passed to PrintString");
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
Zmieniliśmy łańcuch formatujący przekazany do PyArg_ParseTuple tak, aby akceptować dowolny obiekt Pythona. Zmienna o jest ustawiana, by wskazywać na ten obiekt, podczas gdy PyArg_ParseTuple zwraca odsyłacz pożyczony do obiektu. To oznacza, że nie trzeba zmniejszać o jeden zliczenia odsyłaczy dla obiektu.
Funkcja PyString_Check jest wykorzystana dla sprawdzenia czy przekazany obiekt jest łańcuchem. Jeśli tak jest, wartość łańcucha zostaje wydrukowana. W przeciwnym razie, jeśli obiekt jest liczbą całkowitą zostaje wydrukowana wartość całkowita. Wreszcie, wyjątek TypeError jest zgłoszony, jeśli przekazany obiekt nie jest ani łańcuchem ani liczbą całkowitą, ani też nie może być sprowadzony (ang. coerced) do żadnych z tych typów z uwagi na to, że nie obsługuje specjalnych metod Pythona __str__ ani __int__.
Aby przetestować wprowadzone zmiany, przebudowujemy capi.c i ładujemy nową wersję modułu do Pythona:
>>> import capi
>>> capi.PrintString("hej tam")
print string: hej tam
>>> capi.PrintString(1)
print int: 1
>>> capi.PrintString(None)
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: invalid type passed to PrintString
>>> capi.PrintString(1,"string")
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: PrintString requires exactly 1 argument; 2 given
>>>
Zmiany pracują zgodnie z oczekiwaniami. Niemniej jednak, warto zauważyć, że PyArg_ParseTuple nadal zgłasza wyjątek w przypadku, gdy dwa argumenty są przekazane do PrintString.
Globalna blokada interpretera
Zanim przejdziemy do dalszej części opisu tworzenia funkcji wywoływalnych z Pythona, musimy omówić zagadnienie globalnej blokady interpretera Pythona (ang. Python Global Interpreter Lock). Chociaż Python jest zdolny do przetwarzania wielowątkowego (ang. multi-threading), to w danej chwili wewnętrznie tylko jeden wątek (ang. thread) może wykonywać kod Pythona. Aby umożliwić wielowątkowym programom w Pythonie poprawne działanie, funkcje C muszą zwolnić globalną blokadę interpretera przed wywołaniem dowolnej funkcji z biblioteki C, która może zablokować wątek.
Przykładowo, załóżmy, że funkcja czyta jakiś plik lub gniazdo — dokonanie pełnego odczytu może potrwać kilka milisekund. Należy zwolnić globalną blokadę interpretera przed wywołaniem funkcji odczytu, a następnie przejąć ponownie blokadę po zakończeniu działania funkcji.
Python udostępnia dwie proste makrodefinicje, które ułatwiają to zadanie:
int rc;
Py_BEGIN_ALLOW_THREADS
rc = read(...);
Py_END_ALLOW_THREADS
Należy pamiętać, że te makrodefinicję nic nie robią, jeśli Python został skompilowany bez obsługi wątków. Również należy pamiętać, że nie wolno wywoływać żadnych funkcji C API Pythona wtedy, kiedy blokada jest zwolniona.
Tworzenie nowych typów obiektów Pythona
Interfejs C API Pythona może być wykorzystany do tworzenia nowych typów obiektów bezpośrednio w C. Zwykle jest łatwiej utworzyć nowe typy w Pythonie oraz wprowadzić te typy do funkcji C API niższego poziomu, aniżeli tworzyć całkowicie nowy typ w języku C. Jednakże, są pewne sytuacje, które czynią nieodzownym prostotę użytkowania (żaden plik .py nie jest wymagany) lub odrobinę szybsze działanie.
Python alokuje wszystkie obiekty ze sterty (ang. heap). Każda kopia obiektu ma odpowiadający jej typ, zliczenie odsyłaczy oraz zarezerwowaną w pamięci lokalizację, do przechowywania każdego egzemplarza kopii. Aby utworzyć nowy typ obiektu, należy najpierw zdefiniować strukturę (lub klasę C++), która przechowuje dane dla każdego egzemplarza kopii tego obiektu. Musimy także utworzyć strukurę typu obiektu, która definiuje interfejsy obsługiwane przez obiekt.
Diagram ten przedstawia związki pomiędzy:
kopiami obiektów Pythona (pokazane jako kółka),
odpowiadającymi im obszarom zaalokowanej pamięci (sześciokąty),
zliczeniami odsyłaczy (małe kwadraty),
oraz ich współużytkowaną strukturą typu obiektu.
Wszystkie kopie danego typu współużytkują pojedynczy obiekt struktury typu (prostokąt).
Zawiera informacje o typie obiektu, takie jak nazwa typu obiektu, metody dealokacji pamięci dla każdego egzemplarza kopii oraz uzyskiwanie i ustawianie atrybutów. Python udostępnia definicje struktury typu obiektu, tak więc programiści mają jedynie wypełnić puste miejsca odpowiednią informacją.
Obiekty Pythona obsługują wielokrotne interfejsy, to znaczy pojedynczy obiekt może być użyty jako typ liczbowy, słownik, ciąg, itd. W ogólności, każde z tych zastosowań jest zdefiniowane za pomocą pojedynczego interfejsu. Każdy interfejs typu obiektu jest zaimplementowany przez funkcję specyficzną dla tego interfejsu i tego typu obiektu. Python realizuje dostęp do tych interfejsów poprzez stowarzyszoną „szczelinę” (ang. slot) w strukturze typu obiektu, taką jak tp_getattr, tp_setattr, itd.
Obiekty, które nie obsługują danego interfejsu pozostawiają stowarzyszoną szczelinę ustawioną na NULL.
Minimalny typ obiektu
Zmodyfikujmy capi.c tak, aby utworzyć minimalny typ obiektu. Python udostępnia kilka definicji struktur w object.h, które definiują wymagany układ struktury typu obiektu i pokrewnych struktur wspomagających. Trzeba jedynie włączyć Python.h i użyć tych struktur o zdefiniowania podstawowego obiektu:
#include "Python.h"
typedef struct {
PyObject_HEAD /* wymagany naglowek */
int value;
} BasicObject;
Najpierw definiujemy strukturę obszaru pamięci dla każdego egzemplarza kopii nowego typu. Struktura definicji typu BasicObject zawiera Py_Object_HEAD jako swój pierwszy element. Ten wymagany nagłówek zawiera zliczenie odsyłaczy dla kopii, wraz ze wskaźnikiem do struktury typu obiektu. Dodaliśmy także int value jako atrybut dla każdego egzemplarza kopii:
static void
bobject_dealloc(BasicObject *self)
{
if(self)
free(self);
}
Następnie definiujemy funkcję bobject_dealloc dla BasicObject. Będzie ona wywołana kiedy zliczenie odsyłaczy dla obiektu spadnie do zera. Funkcja bobject_dealloc powinna wykonać wszystkie niezbędne porządki dla każdego egzemplarza kopii a potem zwolnić pamięć zaalokowaną dla obiektu. Należy zauważyć, że funkcja bobject_dealloc nie zwraca wartości. Funkcja otrzymuje wskaźnik do obszaru pamięci zajmowanego przez każdy z egzemplarzy kopii, który jest strukturą BasicObject.
PyTypeObject BasicObject_Type = {
PyObject_HEAD_INIT(&PyType_Type) /* wymagany naglowek */
0, /* zmienny rozmiar obiektu */
"basic object", /* nazwa typu obiektu */
sizeof(BasicObject), /* rozmiar pamieci dla kazdej kopii */
0, /* rozmiar indywidualnego elementu */
(destructor) bobject_dealloc /* tp_dealloc */
};
Powyżej tworzymy minimalną strukturę typu obiektu. PyObject_HEAD_INIT jest wymagany jako pierwszy element tej struktury. PyTypeObject deklaruje układ struktury typu obiektu. Wypełniamy odpowiednio szczeliny danymi specyficznymi dla nowego typu obiektu. W tym przypadku, obsługujemy jedynie szczelinę interfejsu, tp_dealloc.
Python pozwala na tworzenie obiektów zawierających tablicę elementów, których rozmiar jest określony podczas alokacji obiektu. Aby tego dokonać, używa się pola rozmiaru indywidualnego elementu i zmiennego rozmiaru obiektu. W naszych przykładach nie będziemy tworzyć obiektów o zmiennej długości.
Patrz object.h dla uzyskania więcej informacji na temat obiektów tablic o zmiennej długości.
Należy zauważyć, że w naszym przykładzie minimalnego typu obiektu wypełniliśmy tylko trzy szczeliny w strukturze typu obiektu:
Nazwę typu obiektu: "basic object"
Rozmiar pamięci dla każdego egzemplarza kopii: sizeof(BasicObject)
Funkcję dealloc: bobject_dealloc
Trzeba teraz dodać funkcję, która tworzy nowe obiekty BasicObject. Następująca funkcja zwróci BasicObject:
PyObject *
NewBasicObject(PyObject *self, PyObject *args)
/* zwraca nowa kopie BasicObject */
{
if(!PyArg_ParseTuple(args, ":BasicObject"))
return NULL;
return (PyObject *) PyObject_NEW(BasicObject, &BasicObject_Type);
}
Ponieważ konstruktor BasicObject nie akceptuje żadnych argumentów, funkcja PyArg_ParseTuple z pustym łańcuchem zostaje wywołana tak, by mieć pewność, że żadne argumenty nie zostały przekazane do konstruktora. Jeśli argumenty są przekazane do konstruktora, funkcja PyArg_ParseTuple zgłosi wyjątek.
Makrodefinicja PyObject_NEW alokuje pamięć dla każdego egzemplarza kopii podanych danych (BasicObject), ustawia typ obiektu na BasicObject_Type i ustawia zliczenie odsyłaczy na jeden. Nowe kopie tworzone przy pomocy tej makrodefinicji są odsyłaczami własnościowymi i mogą być zwrócone bezpośrednio do Pythona.
Wreszcie, modyfikując tablicę capi_functions typu PyMethodDef sprawiamy, że funkcja NewBasicObject jest wywoływalna poprzez moduł capi:
static PyMethodDef capi_functions[] = {
{ "PrintString", PrintString, METH_VARARGS},
{ "BasicObject", NewBasicObject, METH_VARARGS},
{ NULL, NULL }
};
Nie ma specjalnego związku pomiędzy nazwą funkcji NewBasicObject a zwracanym przez nią BasicObject. Każda funkcja C wywoływalna z Pythona może tworzyć nowe obiekty dowolnego typu. W odróżnieniu od C++, to nie nazwa funkcji czyni ją konstruktorem. Raczej tym co czyni jej zachowanie podobnym do konstruktora C++ jest kod, który funkcja wykonuje, zwracając nowe obiekty.
Nadszedł moment, by przetestować nowy podstawowy obiekt w Pythonie BasicObject:
>>> import capi
>>> obj = capi.BasicObject()
<basic_object object at 80c4f58>
>>> obj.value
Traceback (innermost last):
File "<stdin>", line 1, in ?
AttributeError: 'basic_object' object has no attribute 'value'
>>> del obj
>>> ^D
W przedstawionej próbie zaimportowaliśmy moduł capi i stworzyliśmy nową kopię BasicObject. Funkcja repr zwraca typ obiektu jako basic_object, wartość określoną w strukturze object_type. Próbujemy wydrukować atrybut value dla indywidualnego obiektu, ale powoduje to zgłoszenie wyjątku.
Na koniec, usuwamy kopię obiektu.
Obsługa getattr
Podczas realizacji dostępu do atrybutów kopii, Python usiłuje użyć interfejsu typu getattr. Ten interfejs typu nie został jednak zdefiniowany w BasicObject. Uzupełnijmy to teraz, dodając obsługę getattr. Zwróćmy uwagę, że dołączamy structmember.h dla uzyskania dostępu do pewnych funkcji narzędziowych:
#include "Python.h"
#include "structmember.h"
typedef struct {
#define OFF(x) offsetof(BasicObject, x)
static struct memberlist bobject_memberlist[] = {
{"value", T_INT, OFF(value)},
{NULL}
};
static PyObject *
bobject_getattr(BasicObject *self, char *attr)
{
return PyMember_Get((char *) self, bobject_memberlist, attr);
}
Następnie, definiujemy OFF, przydatną makrodefinicję do obliczania przesunięcia podanego elementu struktury. Jest to używane w tablicy bobject_memberlist, która definiuje listę atrybutów dla indywidualnych kopii i każdego typu danych Pythona. W naszym przykładzie, atrybut value jest T_INT. Plik structmember.h definiuje inne typy danych Pythona, które są obsługiwane przy użyciu struktury memberlist.
Funkcja bobject_getattr używa PyMember_Get (zadeklarowanego w structmember.h) dla zlokalizowania podanego atrybutu i zwrócenia jego wartości. PyMember_Get automatycznie zgłasza wyjątek jeśli podany atrybut nie jest odnaleziony — w przeciwnym razie zwraca własnościowy PyObject z poprawną wartością, która może być zwrócona bezpośrednio do Pythona.
W dalszej kolejności, dodajemy bobject_getattr do struktury BasicObject_Type:
(destructor) bobject_dealloc, /* tp_dealloc */
0, /* tp_print, aka str */
(getattrfunc) bobject_getattr, /* tp_getattr */
}
Należy zauważyć, że opuściliśmy szczelinę tp_print, nadając jej wartość zero. Oznacza to, że jest ona niezdefiniowana i nie będzie użyta przez Pythona. Możemy teraz przetestować wprowadzone zmiany:
>>> import capi
>>> obj = capi.BasicObject()
>>> print obj.value
65536
>>> obj.value = 1
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: object has read-only attributes
>>> ^D
Tworzymy tutaj kopię BasicObject i drukujemy jej wartość jej atrybutu value. Należy zauważyć, że value nie równa się 0, jak by tego można oczekiwać. Wynika to stąd, że PyObject_NEW używa malloc, a nie calloc dla zaalokowania pamięci dla indywidualnej kopii tych danych. Trzeba więc jawnie ustawić początkowe wartości dla wszystkich atrybutów indywidualnych kopii.
Próbujemy również ustawić wartość atrybutu value ale to powoduje zgłoszenie wyjątku, ponieważ metoda settatr nie jest zdefiniowana.
Obsługa setattr
Dodanie obsługi dla setattr jest bardzo podobne do dodania obsługi getattr. Wprowadzamy kilka prostych zmian do capi.c:
static int
bobject_setattr(BasicObject *self, char *attr, PyObject *value)
{
if(value == NULL) {
PyErr_SetString(PyExc_AttributeError,"can't delete attributes");
return -1;
}
return PyMember_Set((char *) self, bobject_memberlist, attr, value);
}
Rozpoczynamy od dodania funkcji bobject_setattr. Należy zwrócić uwagę, że wartość zwracana dla tej funkcji jest typu całkowitego a nie PyObject. Każdy interfejs typu ma swoje własne wymagania dla typu zwracanego i typu argumentu — nie zawsze będą one takie same. Funkcja bobject_setattr sprawdza czy value jest NULL. Jeśli tak jest, to oznacza, że Python próbuje usunąć atrybut całkowicie. Nie ma to sensu dla obiektu zdefiniowanego w języku C, zatem zgłaszamy wyjątek.
Funkcja PyMember_Set (zadeklarowana w structmember.h) zwraca właściwą wartość kodu powrotu, zgłaszając wyjątek jeśli podany atrybut nie może być ustawiony, lub podana wartość value jest niewłaściwego typu:
0, /* tp_print, aka str */
(getattrfunc) bobject_getattr, /* tp_getattr */
(setattrfunc) bobject_setattr, /* tp_setattr */
Na koniec, dodajemy bobject_setattr do struktury BasicObject_Type i można już przystąpić do prób:
>>> import capi
>>> obj = capi.BasicObject()
>>> obj.value = 1
>>> print obj.value
1
>>> del obj.value
Traceback (innermost last):
File "<stdin>", line 1, in ?
AttributeError: can't delete attributes
>>> obj.missing = 1
Traceback (innermost last):
File "<stdin>", line 1, in ?
AttributeError: missing
Należy zauważyć, że próba usunięcia atrybutu z obiektu powoduje zgłoszenie wyjątku. Próba ustawienia wartości atrybutu missing, który nie jest składnikiem kopii także spowoduje zgłoszenie wyjątku.
Obsługa wywołań metod
Wywołania metod obiektu są implementowane w podobny sposób jak funkcje modułu. Tablica struktur PyMethodDef definiuje metody, które mogą być wywołane wobec szczególnego typu obiektu:
static PyObject *
bobject_stringvalue(BasicObject *self, PyObject *args)
{
char text[128];
if(!PyArg_ParseTuple(args,":stringvalue"))
return NULL;
sprintf(text,"value is: %d",self->value);
return PyString_FromString(text);
}
Najpierw definiujemy metodę zwaną bobject_stringvalue. Ta metoda nie pobiera argumentów i zwraca łańcuch Pythona zawierający value dla indywidualnego egzemplarza kopii. W odróżnieniu od funkcji modułu, które otrzymują wartość NULL dla self, funkcje metod otrzymują self ustawione na wartość danych dla indywidualnych egzemplarzy kopii. W taki sposób metody dla obiektów zyskują dostęp do danych dla indywidualnych egzemplarzy kopii.
Dla zachowania prostoty, nie sprawdzamy możliwego przepełnienia bufora w tym przykładzie. W praktyce jednakże jest ważne aby zawsze sprawdzać warunki występowania takich potencjalnych błędów.
static PyMethodDef bobject_methods[] = {
{"stringvalue",(PyCFunction) bobject_stringvalue, METH_VARARGS},
{ NULL, NULL}
};
Tablica struktur PyMethodDef wyszczególnia wszystkie metody obsługiwane przez nasz obiekt. W tym przypadku tylko jedna metoda jest obsługiwana: stringvalue.
static PyObject *
bobject_getattr(BasicObject *self, char *attr)
{
PyObject *res;
res = Py_FindMethod(bobject_methods, (PyObject *) self,attr);
if(NULL != res)
return res;
else {
PyErr_Clear();
return PyMember_Get((char *) self, bobject_memberlist, attr);
}
}
Modyfikujemy bobject_getattr tak, aby przeglądać bobject_methods przy pomocy Py_FindMethod w poszukiwaniu podanej metody. Jeśli metoda pasująca do nazwy atrybutu jest odnaleziona, to zwracamy tę metodę do Pythona. W przeciwnym razie, określa się przy użyciu PyMember_Get czy atrybut o podanej nazwie istnieje.
Funkcja Py_FindMethod zgłosi wyjątek, jeśli podanego atrybutu nie ma w tablicy bobject_methods. Używa się PyErr_Clear dla anulowania wyjątku przed wywołaniem PyMember_Get. Jeśli bobject_getattr zwraca obiekt metody powiązanej z Pythonem (dostarczony przez PyFindMethod), to Python wywoła tę metodę, przekazując doń jako self dane dla indywidualnego egzemplarza kopii.
>>> import capi
>>> obj = capi.BasicObject()
>>> obj.value = 1
>>> print obj.stringvalue()
value is: 1
>>> print obj.stringvalue("spam")
Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: stringvalue requires exactly 0 arguments; 1 given
>>> print obj.stringvalue
<built-in method stringvalue of basic_object object at 80c4f58>
>>>
Widzimy tutaj, że funkcja stringvalue zachowuje się zgodnie z oczekiwaniami — nie zaakceptuje argumentów oraz zgłosi wyjątek, kiedy są do niej przekazane argumenty. Wreszcie, wydrukowanie obj.stringvalue pokazuje, że to jest obiekt metody powiązanej.
Inicjalizacja obiektu
Widzieliśmy już wyżej, że atrybut value struktury BasicObject nie był ustawiony na jakąś szczególną wartość przy utworzeniu obiektu. Podobnie jak dla metody __init__ dla klasy, obiekty języka C mogą także inicjalizować się samodzielnie oraz akceptować argumenty konstruktora w czasie inicjalizacji. Aby dodać obsługę argumentów konstruktora, trzeba zmienić funkcję NewBasicObject tak, aby akceptowała argumenty. Wygląda to tak:
PyObject *
NewBasicObject(PyObject *self, PyObject *args)
/* zwraca nowa kopie BasicObject */
{
int defaultValue = 0;
BasicObject *obj;
if(!PyArg_ParseTuple(args,"|i:BasicObject", &defaultValue))
return NULL;
obj = PyObject_NEW(BasicObject, &BasicObject_Type);
if(obj)
obj->value = defaultValue;
return (PyObject *) obj;
}
PyArg_ParseTuple ma nowy łańcuch formatujący, który akceptuje opcjonalną liczbę całkowitą. Należy zauważyć, że defaultValue ma ustawioną wartość domyślną przed wywołaniem PyArg_ParseTuple. Jeśli nie ma podanych argumentów, to PyArg_ParseTuple nie zmieni zmiennej defaultValue.
Wywołujemy PyObject_NEW dla utworzenia nowej kopii struktury BasicObject. Jeśli obiekt został pomyślnie utworzony to wartość atrybutu value jest ustawiona na defaultValue.
>>> import capi
>>> obj = capi.BasicObject(5)
>>> print obj.value
5
>>> xobj = capi.BasicObject()
>>> print xobj.value
0
>>>
Hermetyzacja obiektów C++ przy pomocy interfejsu C API
W przykładzie BasicObject użyto struktury C dla przechowywania indywidualnego egzemplarza kopii, oraz indywidualnych funkcji C jako funkcji interfejsu. Python pozwala na użycie klas C++ jako obiektów Pythona. Jedna niedogodność użycia C++ dla utworzenia obiektów Pythona to ta, że wszystkie funkcje interfejsu typu Pythona muszą być zadeklarowane jako metody statyczne. Jest tak ponieważ Python używa jawnie argumentu self w celu przekazywania danych dla indywidualnych egzemplarzy kopii.
Oto BasicObject zapisany ponownie, tym razem w postaci modułu C++ nazwanego cplus.cpp:
#include <stdio.h>
#include "Python.h"
#include "structmember.h"
class BasicObject: public PyObject
{
public:
BasicObject(int defaultValue=0);
~BasicObject();
/* python specific interfaces */
static void dealloc(PyObject *obj);
static PyObject *getattr(PyObject *obj, char *name);
static int setattr(PyObject *obj, char *name, PyObject *value);
static PyObject *stringvalue(PyObject *obj, PyObject *args);
static struct memberlist memberlist[];
static PyMethodDef methodlist[];
int value;
};
BasicObject::~BasicObject()
{
/* nothing to do here */
}
void
BasicObject::dealloc(PyObject *obj)
{
delete (BasicObject *) obj;
}
#define OFF(x) offsetof(BasicObject, x)
struct memberlist BasicObject::memberlist[] = {
{"value", T_INT, OFF(value)},
{NULL}
};
PyObject *
BasicObject::stringvalue(PyObject *obj, PyObject *args)
{
BasicObject *self = (BasicObject *) obj;
char text[128];
if(!PyArg_ParseTuple(args,":stringvalue"))
return NULL;
sprintf(text,"value is: %d",self->value);
return PyString_FromString(text);
}
PyMethodDef BasicObject::methodlist[] = {
{"stringvalue",(PyCFunction) BasicObject::stringvalue, METH_VARARGS},
{ NULL, NULL}
};
PyObject *
BasicObject::getattr(PyObject *obj, char *attr)
{
PyObject *res;
BasicObject *self = (BasicObject *) obj;
res = Py_FindMethod(methodlist, (PyObject *) self,attr);
if(NULL != res)
return res;
else {
PyErr_Clear();
return PyMember_Get((char *) self, memberlist, attr);
}
}
int
BasicObject::setattr(PyObject *obj, char *attr, PyObject *value)
{
BasicObject *self = (BasicObject *) obj;
if(value == NULL) {
PyErr_SetString(PyExc_AttributeError,"can't delete attributes");
return -1;
}
return PyMember_Set((char *) self, memberlist, attr, value);
}
PyTypeObject BasicObject_Type = {
PyObject_HEAD_INIT(&PyType_Type) /* required header */
0, /* variable object size */
"basic_object", /* name of object type */
sizeof(BasicObject), /* size of per-instance memory */
0, /* size of per-item element */
(destructor) BasicObject::dealloc,/* tp_dealloc */
0, /* tp_print aka str */
(getattrfunc) BasicObject::getattr,/* tp_getattr */
(setattrfunc) BasicObject::setattr,/* tp_setattr */
};
BasicObject::BasicObject(int defaultValue=0)
{
ob_type = &BasicObject_Type;
_Py_NewReference(this);
value = defaultValue;
}
Konstruktor podstawowego obiektu BasicObject akceptuje defaultValue jako początkową wartość value:
PyObject *
NewBasicObject(PyObject *self, PyObject *args)
/* return a new instance of basicobject */
{
int defaultValue = 0;
if(!PyArg_ParseTuple(args,"|i:BasicObject",&defaultValue))
return NULL;
return new BasicObject(defaultValue);
}
Funkcja NewBasicObject jest w istocie tą funkcją, którą Python wywołuje dla utworzenia kopii struktury BasicObject. Akceptuje ona opcjonalny argument całkowity (domyślnie 0):
static PyMethodDef cplus_functions[] = {
{ "BasicObject", NewBasicObject, METH_VARARGS},
{ NULL, NULL }
};
extern "C"
void
initcplus()
{
Py_InitModule("cplus",cplus_functions);
}
Pod każdym względem BasicObject w module cplus funkcjonuje identycznie do BasicObject z modułu capi. Zauważyć należy użycie modyfikatora extern "C" dla funkcji initcplus dla uniknięcia zniekształcenia nazw (ang. name-mangling) przez C++.
Wbudowywanie Pythona do programów C/C++
Jak już zauważyliśmy, interpreter Pythona może być również wbudowany (ang. embedded) do innych programów, nadając owym programom macierzystym (ang. host program) możliwości skryptowe Pythona. Proste wbudowanie interpretera Pythona do programu macierzystego nie jest szczególnie użyteczne samo w sobie — program macierzysty powinien rozszerzyć Pythona w jakiś sposób, poprzez dodanie funkcji, wywołań zwrotnych lub nowych typów obiektów.
Na przykład, można wbudować interpreter Pythona do edytora tekstu. Użytkownicy programu mogliby wówczas tworzyć skrypty Pythona, które wykonywałyby edycję tekstu poprzez wywoływanie funkcji w obrębie edytora tekstu, takich jak „przesuń do”, „oznacz”, „kopiuj” i tak dalej.
Programy macierzyste mogą rozszerzać Pythona używając technik, które już poznaliśmy — używając SWIG lub pisząc samodzielnie funkcje owijające. W trakcie tego podrozdziału, użyjemy obu tych technik dla pokazania pewnych prostych przykładów wbudowania.
Środowisko programowania dla wbudowywania
Pierwszym krokiem na drodze do wbudowania interpretera Pythona jest ustawienie środowiska programowania. Pierwszy przykład wbudowania będzie oparty na pojedynczym pliku źródłowym C, ebdemo.c.
Rozpoczniemy tworząc odpowiedni plik Makefile dla rozważanego przykładu. Najprościej utworzyć plik Makefile, który pracuje z Pythonem, odwołując się do pliku już utworzonego przez Makefile.pre.in w procesie omówionej już inicjacji. Trzeba wydobyć niezbędne opcje z takiego pliku Makefile dla utworzenia pliku, który jest charakterystyczny dla naszego przykładu wbudowania.
Oto plik Makefile, który będzie używany w pierwszym przykładzie:
# Demo Makefile dla ebdemo.c
VERSION= 1.5
CC= gcc
OPT= -g -O2
LIBS= -ldl -lpthread -lieee
installdir= /usr
exec_installdir=/usr
DEFS=-DHAVE_CONFIG_H
INCLUDEDIR= $(installdir)/include
INCLUDEPY= $(INCLUDEDIR)/python$(VERSION)
EXECINCLUDEPY= $(exec_installdir)/include/python$(VERSION)
LIBP= $(exec_installdir)/lib/python$(VERSION)
LIBPL= $(LIBP)/config
PYTHONLIBS= $(LIBPL)/libpython$(VERSION).a
CFLAGS= $(OPT) -I$(INCLUDEPY) -I$(EXECINCLUDEPY) $(DEFS)
default: ebdemo
ebdemo: ebdemo.o
$(CC) -o ebdemo ebdemo.o $(PYTHONLIBS) $(LIBS)
ebdemo.o: ebdemo.c
$(CC) $(CFLAGS) -c ebdemo.c
Plik ten może wymagać drobnych poprawek uwzględniających rzeczywiste ścieżki dostepu do katalogów w danym systemie.
Wbudowywanie Pythona przy użyciu funkcji wysokiego poziomu
Prosty przykład wbudowania jest opisany w dokumentacji Pythona. Zaczniemy tutaj od podobnego przykładu, zwyczajnie klonującego możliwości programu w Pythonie, który polega na przekazaniu argumentów wiersza poleceń i stdin do interpretera Pythona w celu określenia ich wartości.
Typowy program macierzysty oddziałuje z interpreterem w następującym ciągu zdarzeń:
inicjalizacja interpretera Pythona
inicjalizacja statycznie skonsolidowanych modułów rozszerzenia
wykonanie treści programu macierzystego
zakończenie działania interpretera Pythona
Wstępna wersja ebdemo.c jest bardzo prosta:
/* ebdemo.c - embed python demo */
#include <stdio.h>
#include "Python.h"
int main(int argc, char **argv)
{
int rc;
Py_Initialize();
rc = PyRun_AnyFile(stdin,"???");
Py_Finalize();
return rc;
}
Funkcja main wywołuje Py_Initialize w celu inicjalizacji interpretera Pythona. Następnie jest wywoływana funkcja PyRun_AnyFile, do której przekazano uchwyt pliku dla stdin jako pliku wejściowego oraz ustawiono nazwę pliku na łańcuch "???".
Interpreter Pythona rozpoznaje, że plikiem wejściowym jest stdin i podejmuje działanie w trybie interakcyjnym. Interpreter zrobi to ilekroć dowolny z poniższych warunków jest spełniony:
isatty zwraca logiczną prawdę przy przekazaniu uchwytu pliku
nazwa pliku jest NULL, "stdin" lub "???" — otóż istotnie przekazaliśmy "???" jako nazwę pliku aby zmusić interpreter Pythona do działania w trybie interakcyjnym, nawet jeśli stdin jest plikiem a nie terminalem.
Po zakończeniu działania PyRun_AnyFile zamykamy interpreter Pythona, wywołując Py_Finalize i zwracamy wartość zwróconą z funkcji main.
Wykonujemy polecenie make i uruchamiamy plik wynikowy:
$ make
gcc -g -O2 -I/usr/include/python1.5 -I/usr/include/python1.5 -DHAVE_CONFIG_H -c ebdemo.c
gcc -o ebdemo ebdemo.o /usr/lib/python1.5/config/libpython1.5.a -ldl -lpthread -lieee
$ ./ebdemo
>>> import sys
>>> print sys.path
['/usr/lib/python1.5/', '/usr/lib/python1.5/plat-linux-i386',
'/usr/lib/python1.5/lib-tk', '/usr/lib/python1.5/lib-dynload',
'/usr/lib/python1.5/site-packages']
>>> print sys.argv
Traceback (innermost last):
File "???", line 1, in ?
AttributeError: argv
>>> ^D
$
Zgodnie z oczekiwaniem, interpreter Pythona działa w trybie interakcyjnym.
W powyższym przykładzie interakcyjnym są trzy ważne punkty do zauważenia:
^D (Ctrl D) w wierszu zachęty interpretera powoduje powrót z interpretera Pythona do programu ebdemo.
sys.path nie zawiera bieżącego katalogu roboczego w ścieżce dostępu Pythona. Kiedy uruchamiamy program w Pythonie bez żadnych argumentów, to Python wstawia do sys.path specyfikacje katalogu dla wykonywanego programu (jeśli istnieją). W przykładzie ebdemo nie powiedzieliśmy Pythonowi, jaka jest nazwa programu, zatem jego ścieżka nie jest włączona do sys.path.
sys.argv nie został ustawiony, ponieważ przykład ebdemo nie ustawia tego.
Możemy skorygować te problemy, wprowadzając następujące zmiany do ebdemo.c:
Py_SetProgramName(argv[0]);
Py_Initialize();
PySys_SetArgv(argc, argv);
rc = PyRun_AnyFile(stdin,"???");
Dodano wywołanie Py_SetProgramName, z przekazaniem arg[0] jako nazwy programu — musi to być zrobione przed wywołaniem Py_Initialize. Dodane zostało także wywołanie PySys_SetArgv z przekazaniem biężacych zmiennych argc i argv. Dokonane zmiany można przetestować, przebudowując ebdemo.c i go uruchamiając:
$ ./ebdemo
>>> import sys
>>> print sys.path
['.', '/usr/lib/python1.5/', '/usr/lib/python1.5/plat-linux-i386',
'/usr/lib/python1.5/lib-tk', '/usr/lib/python1.5/lib-dynload',
'/usr/lib/python1.5/site-packages']
>>> print sys.argv
['./ebdemo']
>>> ^D
Jest wiele funkcji wysokiego poziomu dostępnych do wykonywania, analizy składniowej i kompilacji łańcuchów znakowych lub wykonywania plików. Pełny wykaz tych funkcji można znaleźć w dokumentacji do Pythona.
Statyczna konsolidacja programu macierzystego z modułem rozszerzenia
Zauważyliśmy powyżej, że program macierzysty, który nie oferuje żadnego poszerzenia możliwości Pythona nie jest zbyt użyteczny. Zobaczymy teraz jak skonsolidować statycznie moduł rozszerzenia z programem macierzystym. W tym szczególnym przykładzie, skonsolidujemy z plikiem sysinfo_wrap.c, który wyprodukowaliśmy przy użyciu generatora SWIG.
Pierwszym krokiem w procesie konsolidacji jest zmodyfikowanie pliku Makefile w sposób następujący:
ebdemo: ebdemo.o sysinfo.o
$(CC) -o ebdemo ebdemo.o sysinfo.o $(PYTHONLIBS) $(LIBS)
sysinfo.o: ../sysinfo/sysinfo_wrap.c
$(CC) $(CFLAGS) -c ../sysinfo/sysinfo_wrap.c -o sysinfo.o
ebdemo.o: ebdemo.c
$(CC) $(CFLAGS) -c ebdemo.c
W tym szczególnym przykładzie, budujemy ebdemo w bliźniaczym katalogu sysinfo, gdzie rezyduje plik sysinfo_wrap.c. Plik Makefile wykorzystuje względną ścieżkę dostępu do pliku sysinfo_wrap.c.
Trzeba wprowadzić dwie proste zmiany do pliku ebdemo.c:
Po pierwsze, należy zadeklarować funkcję init jak zewnętrzną dla modułu sysinfo:
#include "Python.h"
extern void initsysinfoc();
int main(int argc, char **argv)
Następnie należy dodać wywołanie do procedury inicjalizującej moduł sysinfo:
PySys_SetArgv(argc, argv);
initsysinfoc();
rc = PyRun_AnyFile(stdin,"???");
Jesteśmy niemal gotowi do przetestowania tych zmian. Jednakże, jest jeszcze jeden krok, który trzeba uczynić zanim będzie można użyć moduł sysinfo. W tym miejscu warto sobie przypomnieć, że moduł sysinfo składa się zarówno z pliku sysinfo_wrap.c jak i pliku towarzyszącego (ang. shadow file) nazwanego sysinfo.py. Oba te pliki są produkowane przez generator SWIG w wyniku przetworzenia pliku interfejsowego sysinfo.i.
Kiedy moduł sysinfo jest importowany, to faktycznie Python importuje sysinfo.py, a ten z kolei importuje sysinfoc (moduł rozszerzenia, który sam jest zawarty w sysinfo_wrap.c). Ponieważ sysinfo.py nie został umieszczony w ścieżce Pythona, trzeba znaleźć sposób, aby udostępnić go dla programu ebdemo. Najszybciej to zrobić kopiując sysinfo.py do bieżącego katalogu roboczego, przed rozpoczęciem programu ebdemo:
$ cp ../sysinfo/sysinfo.py .
Typowo, plik sysinfo.py zostałby zainstalowany w ścieżce Pythona, ale dla uproszczenia używamy tu zwykłej metody kopiowania pliku:
$ ./ebdemo
>>> import sysinfo
>>> si = sysinfo.sysinfo()
>>> sysinfo.getsysinfo(si)
0
>>> print si.totalram
97861632
>>>
Tak więc, uruchomiony program ebdemo importuje moduł sysinfo. To importuje plik sysinfo.py, który z kolei importuje moduł sysinfoc. Jako, że moduł sysinfoc już był zainicjalizowany (poprzez wywołanie initsysinfoc w ebdemo.c), Python nie szukał współużytkowanego modułu rozszerzenia sysinfocmodule.so tak jak to czynił we wcześniej rozważanych przykładach z użyciem sysinfo.
Z punktu widzenia Pythona, współużytkowany moduł rozszerzenia sysinfocmodule.so i statycznie skonsolidowany moduł sysinfoc mają dokładnie takie same działanie.
Wbudowywanie Pythona przy użyciu wywołań niższego poziomu
Interfejs programowania C API Pythona wysokiego poziomu ułatwia i przyspiesza wbudowywanie. Jednakże większość aplikacji macierzystych wymaga większej kontroli nad tym, w jaki sposób Python wykonuje skrypty. Programy macierzyste mogą być modułami rozszerzenia poprzez utworzenie ich własnych interfejsów dla modułów oraz zdefiniowanie odpowiedniego zestawu funkcji w obrębie tego modułu. Wykorzystując to, programy macierzyste mogą umożliwić skryptom Pythona wywołanie zwrotne do nich w celu uzyskania lub dostarczenia informacji.
Interfejs programowania C API Pythona pozwala programom macierzystym wywoływać indywidualne funkcje w modułach Pythona, wywoływać metody dla kopii obiektu oraz sprawnie obsługiwać słowniki modułów (ang. module dictionaries). Taki zestaw funkcji dostarcza środków bardziej subtelnej kontroli nad środowiskiem wykonania niż same funkcje wysokiego poziomu.
Zademonstrujemy teraz użycie niektórych funkcji C API niższego poziomu.
Wykonywanie łańcuchów
Zmodyfikujmy ebdemo.c tak, aby dodać odrobinę więcej możliwości:
Py_SetProgramName(argv[0]);
Py_Initialize();
PySys_SetArgv(argc, argv);
initsysinfoc();
if(argc < 2) {
PySys_SetArgv(argc, argv);
rc = PyRun_AnyFile(stdin,"???");
} else {
PySys_SetArgv(argc-1, argv+1);
sprintf(cmd,"execfile('%s')",argv[1]);
rc = PyRun_SimpleString(cmd);
}
printf("Result Code is %d\n",rc);
Py_Finalize();
return rc;
Dzięki wprowadzonym zmianom do ebdemo.c, argument wiersza poleceń może być akceptowany. Jeśli taki argument zostanie podany, to jest on wykonany przez Pythona jako skrypt przy użyciu funkcji execfile. W takim przypadku, argumenty przekazane do skryptu Pythona także są modyfikowane — zostaje wyeliminowany argv[0] z listy argumentów. Funkcja PyRun_SimpleString jest wykorzystana do wykonania instrukcji execfile. Jeśli program ebdemo jest uruchomiony bez argumentu wiersza poleceń to, jak w poprzedniej wersji, interpreter Pythona działa w trybie interakcyjnym.
Na koniec dodano instrukcję print dla pokazania wartości wyniku zwróconego z Pythona.
Utwórzmy także prosty plik skryptowy do przetestowania z programem ebdemo — nazwiemy go ebdemo1.py:
# test ebdemo
print "hi, ebdemo running Python"
import sys
print sys.argv
Trzeba przebudować ebdemo i uruchomić go wraz z ebdemo1.py jako argumentem wiersza poleceń:
$ ./ebdemo ebdemo1.py arg1 arg2 arg3
hi, ebdemo running Python
['ebdemo1.py', 'arg1', 'arg2', 'arg3']
Result Code is 0
Widać, że ebdemo1.py jest pierwszym argumentem, po którym następują pozostałe argumenty, przekazane z wiersza poleceń. Wartość zwrócona z PyRun_SimpleString to 0.
Zastępowanie wewnętrznych funkcji Pythona
Załóżmy, że uruchamiamy ebdemo bez żadnych argumentów wiersza poleceń i wykonujemy funkcję Pythona sys.exit:
$ ./ebdemo
>>> import sys
>>> sys.exit(5)
$
Warto zauważyć, że wartość zwrócona z PyRun_AnyFile nie jest pokazana. Funkcja Pythona sys.exit bezpośrednio wywołuje funkcję exit z biblioteki uruchomieniowej C i w ten sposób program ebdemo kończy działanie. Takie zachowanie jest niepożądane we wbudowanym programie.
Można zastąpić funkcję sys.exit (lub dowolną inną funkcję) poprzez zadeklarowanie własnej funkcji w C do wywołania w jej miejsce.
Dodajmy następujący kod do ebdemo.c:
extern void initsysinfoc();
PyObject *
NoExit(PyObject *a, PyObject *args)
/* disallow all exiting */
{
PyErr_SetString(PyExc_RuntimeError,"No exit allowed");
return NULL;
}
static PyMethodDef hostModuleMethods[] = {
{ "NoExit", NoExit, METH_VARARGS },
{ NULL, NULL}
};
void
SetupHostModule(void)
{
PyObject *hostmod, *sysmod;
hostmod = Py_InitModule("hostModule",hostModuleMethods);
sysmod = PyImport_ImportModule("sys");
if(NULL != sysmod) {
PyObject *sysDict, *noExitObj, *hostDict;
sysDict = PyModule_GetDict(sysmod);
hostDict = PyModule_GetDict(hostmod);
if(NULL != sysDict && NULL != hostDict) {
noExitObj = PyDict_GetItemString(hostDict,"NoExit");
if(NULL != noExitObj)
PyDict_SetItemString(sysDict,"exit",noExitObj);
}
Py_DECREF(sysmod);
}
}
To co zostało dodane, to dwie funkcje i struktura inicjalizacji modułu. Funkcja NoExit jest standardową funkcją C wywoływalną z Pythona, która po prostu zgłasza wyjątek RuntimeError.
Funkcja SetupHostModule inicjalizuje moduł hostModule. Następnie uzyskuje odsyłacz do modułu sys, lokalizuje funkcję exit w słowniku dla modułu i zamienia ją na funkcję NoExit, zdefiniowaną w module hostModule.
Wreszcie, w trakcie inicjalizacji, wywołujemy funkcję SetupHostModule:
Py_Initialize();
initsysinfoc();
SetupHostModule();
if(argc < 2) {
Można przetestować wprowadzone zmiany w następujący sposób:
$ ./ebdemo
>>> import sys
>>> sys.exit(5)
Traceback (innermost last):
File "???", line 1, in ?
RuntimeError: No exit allowed
>>> ^D
Result Code is 0
$
Można użyć tej metody do zastąpienia jakiejkolwiek innej funkcji. Jednakże nie jest to prawdopodobnie najlepszy sposób postępowania z sys.exit, ponieważ większość skryptów Pythona spodziewa się raczej zakończenia, aniżeli napotkania wyjątku używającego tej funkcji.
Należy zwrócić uwagę, że złożony proces zastępowania sys.exit przez hostmodule.NoExit może być uproszczony przez wykonanie przypisania w Pythonie zamiast w C.
Wywoływanie funkcji Pythona
Dowolna funkcja zdefiniowana w skrypcie Pythona na poziomie modułu lub klasy może być wywołana z programu macierzystego. Jako prosty przykład, rozważmy modyfikację funkcji C NoExit, pozwalającą na odwołania zwrotne do Pythona. Funkcja ta będzie szukać w module __main__ (zasadniczy moduł wykonywany przez interpreter Pythona) funkcji nazwanej ExitFunction. Jeśli ta funkcja istnieje, to NoExit ją wywoła, przekazując doń argument w postaci łańcucha:
PyObject *
NoExit(PyObject *a, PyObject *args)
/* disallow all exiting */
{
PyObject *mainModule, *mainDict;
mainModule = PyImport_ImportModule("__main__");
if(NULL != mainModule) {
PyObject *mainDict;
mainDict = PyModule_GetDict(mainModule);
if(NULL != mainDict) {
PyObject *func =
PyDict_GetItemString(mainDict,"ExitFunction");
if(NULL != func) {
PyObject *res = PyObject_CallFunction(func,
"s","NoExit was called");
Py_DECREF(mainModule);
return res;
}
}
Py_DECREF(mainModule);
}
PyErr_SetString(PyExc_RuntimeError,"No exit allowed");
return NULL;
}
Warto zauważyć, że PyImport_ImportModule zwraca odsyłacz własnościowy do wybranego modułu. Ten odsyłacz musi być uwolniony za pomocą makrodefinicji Py_DECREF. Funkcja NoExit używa odsyłacza dla uzyskania słownika modułu, a następnie wywołuje PyDict_GetItemString dla odnalezienia ExitFunction. Funkcja PyDict_GetItemString zwraca pożyczony odsyłacz, który nie powinien być zwolniony.
Na koniec użyjemy PyObject_CallFunction dla wywołania ExitFunction (jeśli jest zdefiniowana). PyObject_CallFunction pobiera łańcuch formatujący podobnie jak w przypadku funkcji Py_BuildValue. W naszym przykładzie, przekazujemy pojedynczy łańcuch do obiektu funkcji.
Aby przetestować powyższe zmiany, uruchamiamy ebdemo bez argumentów wiersza poleceń i korzystamy z interpretera Pythona w trybie interakcyjnym:
$ ./ebdemo
>>> import hostModule
>>> hostModule.NoExit()
Traceback (innermost last):
File "???", line 1, in ?
RuntimeError: No exit allowed
>>> def ExitFunction(s):
... print "ExitFunction called with '", s, "'"
... return "I got " + s
...
>>> print hostModule.NoExit()
ExitFunction called with ' NoExit was called '
'I got NoExit was called'
>>>
Najpierw wywołujemy hostModule.NoExit dla pokazania początkowego zachowania (zgłasza wyjątek). Definiujemy funkcję nazwaną ExitFunction, która drukuje informację i zwraca inny łańcuch. Jeśli teraz wywołamy hostModule.NoExit to widzimy, że funkcja ExitFunction jest wywołana a wartość zwrócona, przekazana poprzez NoExit, jest wydrukowana.
Tworzenie kopii obiektu w Pythonie oraz wywoływanie jej metody
Ten raczej akademicki przykład zademonstruje jak skonkretyzować obiekt klasy Pythona (utworzyć kopię obiektu) oraz jak wywołać metodę kopii obiektu. Podobnie jak w dwóch poprzednich przykładach, trzeba uzyskać odsyłacz do modułu docelowego, odsyłacz do słownika modułu, a następnie wydobyć pożądany element ze słownika.
Do ebdemo.c dodana jest następująca funkcja:
void
CallMethodInInstance(char *klassname, char *method)
{
PyObject *mainModule, *mainDict, *klassobj, *args, *instance;
mainModule = PyImport_ImportModule("__main__");
if(NULL == mainModule)
return;
mainDict = PyModule_GetDict(mainModule);
Py_DECREF(mainModule);
if(NULL == mainDict)
return;
klassobj = PyDict_GetItemString(mainDict,klassname);
if(NULL == klassobj) {
printf("Could not find class named %s in __main__\n",klassname);
return;
}
args = Py_BuildValue("()"); /* pusta wielokrotka jest utworzona dla args */
if(NULL == args)
return;
instance = PyInstance_New(klassobj,args,NULL);
if(NULL == instance) {
printf("Could not create instance of %s\n",klassname);
return;
}
PyObject_CallMethod(instance, method, "s", "Call From CMII");
Py_DECREF(instance);
}
Funkcja CallMethodInstance przyjmuje dwa argumenty: nazwę klasy do skonkretyzowania i nazwę metody do wywołania dla utworzonej kopii tej klasy. Po uzyskaniu odsyłacza do słownika modułu, wydobywamy obiekt klasy z tego słownika w oparciu o zmienną klassname.
Kiedy kopia klasy jest utworzona, to jest wywołana metoda specjalna Pythona __init__. W naszym przykładzie, this przyjmie tylko jeden argument: self. Argumenty są przekazane do __init__ w postaci wielokrotki. Pusta wielokrotka jest tworzona przy pomocy funkcji Py_BuildValue.
Następnie wywołujemy PyInstance_New, przekazując odsyłacz do obiektu klasy, pustej wielokrotki oraz NULL, jako słownika słów kluczowych.
Jeśli PyInstance_New tworzy kopię żądanej klasy, to wtedy wykorzystuje się PyObject_CallMethod dla wywołania metody podanej w łańcuchu znakowym metody. Podana metoda jest przekazana w pojedynczym argumencie łańcuchowym "Call from CMII".
Trzeba wprowadzić jeszcze jedną zmianę do ebdemo.c, umieszczając wywołanie do CallMethodInInstance po instrukcji PyRun_SimpleString. Poprzez wykonanie podanego skryptu Pythona w pierwszej kolejności, skrypt może zdefiniować do wywołania docelową klasę (ang. target class). Można wtedy sprawdzić funkcję CallMethodInstance w razie, gdy jednocześnie jest podana nazwa klasy i nazwa metody w wierszu poleceń ebdemo:
rc = PyRun_SimpleString(cmd);
if(argc > 3) /* argv[2] == class, argv[3] == method */
CallMethodInInstance(argv[2],argv[3]);
}
Teraz tworzymy prosty skrypt testowy Pythona zwany ebdemo2.py zawierający następujący kod:
#demo: tworzenie kopii klasy i callback
class ebdemo:
def __init__(self):
print "ebdemo object created"
def Test(self,s):
print "In function Test, got arg '"+s+"'"
Na koniec, testujemy wprowadzone zmiany w sposób następujący:
$ ./ebdemo ebdemo2.py ebdemo Test
ebdemo object created
In function Test, got arg 'Call From CMII'
Result Code is 0
$
Przetwarzanie wielowątkowe
Obsługa wątków (ang. thread support) jest dostępna w Pythonie, o ile używany interpreter został skompilowany z opcją konfiguracji --with-threads. Jeśli zainstalowano Pythona z pliku RPM lub pakietu dystrybucji (lub jeśli został zainstalowany wraz z systemem) najprawdopodobniej obsługa wątków jest włączona.
Używanie wielu wątków w obrębie programów macierzystych w C lub C++ wymaga odrobiny więcej kodu niż proste programy, które dotąd widzieliśmy. Należy pamiętać, że Python ma globalną blokadę interpretera, której używa dla upewnienia się, że tylko jeden wątek wykonuje kod Pythona w danej chwili. Python używa także struktury PyThreadState dla przechowywania informacji dla indywidualnego wątku, takich jak errno i oczekujące wyjątki. Każdy wątek w programie Pythona ma pojedynczą strukturę PyThreadState z nim związaną.
Python może także obsługiwać wiele interpreterów w jednym programie. Stan interpretera (lub struktura PyInterpreterState) zawiera wszystkie informacje uruchomieniowe danego interpretera, takie jak importowane moduły, ścieżka przeszukiwania modułu, sys.stdout i tym podobne.
Każda struktura PyThreadState zawiera odsyłacz do struktury PyInterpreterState. Jednakże więcej niż jedna struktura PyThreadState może zawierać odsyłacz do pojedynczej struktury PyInterpreterState, jak to pokazano na następującym diagramie:
Ilekroć zostaną namnożone (ang. spawn) nowe wątki w obrębie samego Pythona, Python nimi zarządza. Jednakże, aby utworzyć wielowątkowy program macierzysty, który ma wbudowany interpreter Pythona, trzeba zarządzać ręcznie stanami wątków, globalną blokadą interpretera oraz strukturami PyInterpreterState.
tdemo.c
Utwórzmy wielowątkowy program macierzysty nazwany tdemo.c. Będzie używał pojedynczej struktury PyInterpreterState z wieloma strukturami PyThreadState oraz będzie namnażał nowe wątki, ilekroć otrzyma polecenie ze skryptu Pythona. Te wątki będą uśpione na okres podanej liczby sekund, wydrukują komunikat, a potem znowu przejdą dalej w stan uśpienia na pewien czas. Po parokrotnym wydrukowaniu komunikatu, watki zakończą działanie i obumrą.
Rozpoczynamy od włączenia zwykłych plików nagłówkowych. Dodatkowo włączamy pthread.h, aby uzyskać dostęp do funkcji obsługi wątków:
/* tdemo.c - threaded embed python demo */
#include <stdio.h>
#include <pthread.h>
#include "Python.h"
PyThreadState *mainThreadState;
InterpretPythonString
Funkcja ta akceptuje łańcuch zawierający instrukcje Pythona, który wykonuje w kontekście głównego modułu:
int
InterpretPythonString(char *s)
/* return 1 if exiting */
{
PyObject *m, *d, *v;
int gotnl = (NULL != strchr(s,'\n'));
m = PyImport_AddModule("__main__");
if (NULL == m)
return;
d = PyModule_GetDict(m);
v = PyRun_String(s, gotnl ? Py_file_input : Py_single_input, d, d);
if (NULL == v) {
if(PyExc_SystemExit == PyErr_Occurred()) {
PyErr_Clear();
return 1;
}
return 0;
}
Py_DECREF(v);
return 0;
}
Zakładamy, że globalna blokada interpretera jest w posiadaniu bieżącego wątku w chwili wywołania funkcji. Używa ona funkcji PyRun_String dla wykonania dostarczonego łańcucha polecenia. Jeśli ten łańcuch zawiera znaki końca wiersza, to mówimy PyRun_String, że dane wejściowe są typu Py_file_input a nie Py_single_input.
Jeśli PyRun_String zwraca NULL, to świadczy to o zgłoszeniu wyjątku. Sprawdzamy, czy ten wyjątek jest typu PyExec_SystemExit. Jeśli tak jest, zwracamy 1 z InterpretPythonString, w przeciwnym razie zmniejszamy o jeden zliczenie odsyłaczy na wartości zwróconej oraz zwracamy 0.
StartThread
StartThread jest funkcją, która będzie wykonana przez utworzony wątek.
void *
StartThread(void *arg)
{
char cmd[256];
PyThreadState *tstate;
int loopcount = 0;
int i = (int) arg;
sprintf(cmd,"print 'Thread %d says hi!'\n",pthread_self());
PyEval_AcquireLock(); /* przejmuje globalna blokade */
tstate = PyThreadState_New(mainThreadState->interp);
PyThreadState_Swap(tstate); /* uzyj tego nowego stanu watku */
while(1) {
Py_BEGIN_ALLOW_THREADS
sleep(i);
Py_END_ALLOW_THREADS
if(0 != InterpretPythonString(cmd))
break;
if(2 < loopcount++)
strcpy(cmd,"import sys\nsys.exit(1)\n");
}
printf("Thread %d exiting\n",pthread_self());
PyThreadState_Swap(NULL); /* zeruj stan watku Pythona */
PyThreadState_Clear(tstate); /* wyczysc stan watku */
PyThreadState_Delete(tstate); /* usun stan watku */
PyEval_ReleaseLock();
return NULL;
}
Funkcja przejmuje globalną blokadę interpretera przy użyciu funkcji PyEval_AcquireLock. Funkcja PyThreadState_New tworzy nową strukturę stanu wątku, która jest zachowana w zmiennej tstate. Ta funkcja wymaga wskaźnika do struktury PyInterpreterState, której ten wątek będzie używał. Przekazujemy strukturę PyInterpreterState używaną przez główny wątek.
PyThreadState_Swap ustawia bieżący stan wątku na stan nowo utworzony, przechowywany w tstate. Wchodzimy do pętli while i korzystamy z makrodefinicji Py_BEGIN_ALLOW_THREADS dla zwolnienia globalnej blokady interpretera przed wywołaniem funkcji systemowej sleep.
Po powrocie z uśpienia, makrodefinicja Py_END_ALLOW_THREADS odzyskuje globalną blokadę interpretera a następnie przywraca bieżący stan PyThreadState. W tym momencie bieżący wątek może bezpiecznie wykonać funkcje interfejsu C API Pythona. Funkcja StartThread wywołuje InterpretPythonString dla wykonania łańcucha cmd. Jeśli InterpretPythonString zwraca wartość niezerową (ponieważ sys.exit była wywołana w Pythonie) kończymy działanie pętli loop.
Jeśli pętla była wykonana kilka razy, to loopcount sprawi, że cmd zostanie zastąpione przez:
import sys
sys.exit(1)
Zatem przy następnym wykonaniu pętli while, InterpretPythonString wykona nową wartość cmd powodując, że funkcja InterpretPythonString zwróci niezerową wartość.
Na zakończenie, drukujemy komunikat wskazujący, że wątek kończy działanie, ustawiamy bieżący stan wątku do wartości wyjściowej NULL, czyścimy strukturę PyThreadState bieżącego wątku i ją zwalniamy. Ostatnią wykonaną funkcją jest PyEval_ReleaseLock, która zwalnia globalną blokadę interpretera.
LaunchThread
Funkcja LaunchThread jest wywołana z Pythona dla namnożenia nowego wątku:
PyObject *
LaunchThread(PyObject *a, PyObject *args)
/* disallow all exiting */
{
int i = 5;
pthread_t tid;
int rc;
if(!PyArg_ParseTuple(args,"|i:LaunchThread",&i))
return NULL;
if(pthread_create(&tid,NULL,StartThread,(void *) i) < 0)
return PyErr_SetFromErrno(PyExc_RuntimeError);
if(pthread_detach(tid) < 0)
return PyErr_SetFromErrno(PyExc_RuntimeError);
return PyInt_FromLong(tid);
};
Funkcja przyjmuje dodatkowy argument: czas opóźnienia (ang. time delay). LaunchThread tworzy nowy wątek przy użyciu pthread_create, któremu przekazuje do wykonania funkcję StartThread. Funkcja pthread_detach pozwala nowo utworzonemu wątkowi na wykonanie. Zwracamy do Pythona identyfikator nowego wątku.
host_functions
Jest to tablica funkcji wyeksportowanych przez moduł macierzysty:
PyMethodDef host_functions[] = {
{ "LaunchThread", LaunchThread, METH_VARARGS},
{ NULL, NULL }
};
SetupHostModule
Funkcja ta używa tablicy host_functions do inicjalizacji modułu macierzystego (ang. host module):
void
SetupHostModule(void)
{
PyObject *hostmod, *sysmod;
hostmod = Py_InitModule("hostModule",host_functions);
}
InitializePython
Funkcja InitializePython inicjalizuje interpreter Pythona i ustawia początkowe środowisko dla wątków:
void
InitializePython(int argc, char **argv)
{
Py_SetProgramName(argv[0]);
PyEval_InitThreads(); /* inicjalizuj obsluge watkow, przejmij globalna blokade */
Py_Initialize();
PySys_SetArgv(argc, argv);
SetupHostModule();
mainThreadState = PyEval_SaveThread(); /* zwolnij globalna blokade */
}
Wywołuje Py_SetProgramName dla ustawienia nazwy programu, PyEval_InitThreads dla przełączenia Pythona w tryb wątkowy, Py_Initialize dla zainicjalizowania interpretera Pythona, PySys_SetArgv dla ustawienia wartości sys.argv oraz SetupHostModule dla zainicjalizowania hostModule. Na zakończenie PyEval_SaveThread jest wywołana dla odzyskania wskaźnika do stanu wątku PyThreadState i zwolnienia globalnej blokady interrpetera.
Zmienna globalna mainThreadState jest użyta w StartThread dla zwrócenia wskaźnika do struktury PyInterpreterState.
main
W main wywołujemy InitializePython w celu ustawienia początkowego środowiska wątkowego, inicjalizacji Pythona oraz ustawienia modułu hostModule:
int
main(int argc, char **argv)
{
int rc;
char cmd[1024];
InitializePython(argc, argv);
PyEval_AcquireLock(); /* przejmij globalna blokade interpretera */
PyThreadState_Swap(mainThreadState);
rc = PyRun_AnyFile(stdin,"???");
Py_Finalize();
/* po prostu zakoncz bez zwolnienia globalnej blokady tak, aby inne watki nie wywolywaly Pythona */
return rc;
}
Pyeval_AcquireLock przejmuje globalną blokadę interpretera (która była zwolniona przez funkcję InitializePython). Następnie używamy funkcji Py_ThreadState_Swap do ustawienia bieżącego stanu PyThreadState na mainThreadState i wywołujemy PyRun_AnyFile dla uruchomienia interpretera Pythona w trybie interakcyjnym.
Kiedy PyRun_AnyFile kończy działanie to uruchamiamy Py_Finalize dla wyczyszczenia interpretera Pythona i powracamy z main bez zwolnienia globalnej blokady interpretera. Prawdopodobnie byłoby lepiej zaczekać na zakończenie działania namnożonych przez StartThread wątków przed wywołaniem Py_Finalize. Jednakże, ponieważ staramy się utrzymać przykład możliwie prostym, kończymy jak tylko sesja interakcyjna dobiega końca.
Jesteśmy teraz gotowi do przetestowania programu tdemo poprzez wprowadzenie poleceń namnożenia nowych wątków w trybie interakcyjnym:
$ ./tdemo
>>> import hostModule
>>> hostModule.LaunchThread(10)
1026
>>> dir()
Wykonujemy funkcję dir jedynie dla pokazania, że interpreter interakcyjny działa nadal po uruchomieniu wątku 1026 (przy użyciu funkcji LaunchThread). Należy zwrócić uwagę, że numery wątków w systemie Linux są, w gruncie rzeczy numerami procesów, a zatem jest mało prawdopodobne, aby były takie same w kolejnych przebiegach:
['__builtins__', '__doc__', '__name__', 'hostModule']
>>> hostModule.LaunchThread(5)
2051
Uruchamiamy kolejny wątek:
>>> Thread 1026 says hi!
Thread 2051 says hi!
>>> Thread 2051 says hi!
Thread 1026 says hi!
Thread 2051 says hi!
>>> Thread 2051 says hi!
Thread 1026 says hi!
Thread 2051 exiting
Oba wątki drukują swoje komunikaty, a następnie wątek 2051 kończy działanie:
>>> dir()
['__builtins__', '__doc__', '__name__', 'hostModule', 'sys']
To polecenie demonstruje, że interpreter Pythona nadal pracuje, nawet po tym jak wątek 2051 obumarł:
>>> Thread 1026 says hi!
Thread 1026 exiting
>>> hostModule.LaunchThread(1)
3074
>>> Thread 3074 says hi!
Thread 3074 says hi!
Thread 3074 says hi!
Thread 3074 says hi!
Thread 3074 exiting
>>> ^D
Uruchamiamy jeszcze jeden wątek po tym jak pierwsze dwa obumarły, by definitywnie zakończyć program przy pomocy Ctrl D.
Ogólne sugestie
Opracowując nowe programy trzeba pamiętać, że często łatwiej jest modelować nowy program w Pythonie, aniżeli pisać w języku C. Jeśli się okaże, że prototyp nie działa jak tego oczekiwano, można zapisać ponownie „powolną część kodu” w języku C jako moduł rozszerzenia. Takie hybrydowe podejście do opracowania oprogramowania pozwoli na szybsze uzyskanie wyników niż w przypadku pisania wyłącznie programu w języku C.
Programy C/C++ mogą poszerzyć znacznie zakres swoich możliwości w wyniku wbudowania interpretera Pythona. Statyczne programy stają się łatwiejsze do konfiguracji i wszechstronniejsze wskutek umożliwienia ich użytkownikowi sterowania programem za pośrednictwem Pythona. Jak poprzednio, podejście hybrydowe często umożliwia na imponujące skrócenie czasu opracowywania oprogramowania.
Interpreter Pythona może być wykorzystany do zautomatyzowania procesu testowania modułów i systemu programów C/C++. Dzięki włączeniu do programu C w trakcie jego opracowywania oprzyrządowania testowego, można na bieżąco sprawdzać moduły programu przy użyciu skryptów Pythona. Kolejne poprawki do programu mogą być szybko przetestowane i porównane z wynikami kontrolnymi dla wykrycia nieoczekiwanych efektów ubocznych.
Zasoby online
Więcej informacji o Pythonie można znaleźć pod adresem: http://www.python.org.
Aby dowiedzieć się więcej na temat generatora SWIG można zajrzeć pod adres: http://www.swig.org.
Podsumowanie
Rozdział ten demonstruje w jaki sposób można poszerzyć Pythona przy użyciu zarówno generatora SWIG jak i ręcznie napisanego kodu C/C++. Przy użyciu omówionych technik można udostępnić dla skryptów Pythona właściwie każdą funkcję, bibliotekę lub obiekt C++.
Zobaczyliśmy, że generator SWIG może być użyty do szybkiego tworzenia funkcji interfejsu opartych na istniejących plikach nagłówkowych. Jednak, w niektórych przypadkach pewne jego ograniczenia mogą oznaczać konieczność dodatkowego ręcznego dostrojenia.
Niemniej jednak, pliki dla klasy towarzyszącej generatora SWIG zapewniają wszechstronny mechanizm ułatwiający dostęp do struktur, podczas gdy dyrektywa %addmethods udostępnia użyteczne własności hermetyzujące, które często górują nad ograniczeniami SWIG. Badanie utworzonego przez SWIG pliku owijającego C może ujawnić wskazówki co do sposobu użycia interfejsu programowania aplikacji C API Pythona oraz sposobu kształtowania ręcznie pisanego modułu C/C++.
Rysunek pingwina z fajką.
Dyskusja online http://www.p2p.wrox.com
2 Część I ♦ Podstawy obsługi systemu WhizBang (Nagłówek strony)
2 C:\Robert\Helion\CC_3.doc
Rys.1, str. 588
Opis:
Funkcje wywołania
Biblioteka Biblioteka
Interpretera Modułu
Pythona Rozszerzenia
Użyj exportowane C API
Program wykonywalny w Pythonie
Rys. 2, str. 588.
Opis:
Funkcje wywołania
Biblioteka Funkcje modułu
Interpretera rozszerzenia wbudowane
Pythona do programu macierzystego
Użyj eksportowane C API
Program macierzysty
Rys., str. 590.
Opisy:
SWIG Interface File = Plik interfejsowy SWIG
SWIG = SWIG
C Wrapper File = Plik owijający w C
Compile and Link= Kompilacja i konsolidacja
Python Shadow File = Plik towarzyszący Pythona
Shared Library Ext. Module = Moduł zewnętrznej biblioteki współużytkowanej
Python Interpreter = Interpreter Pythona
Tu wstawić diagram ze strony 645 — przyp. tłum.
Opisy:
Interpreter state = stan interpretera
Thread state = stan wątku
Thread = wątek
Rys. ze str. 624
Opisy:
Instance of object = kopia obiektu
Allocated Memory = zaalokowana pamięć
Object Type = typ obiektu
Object type description structure = opis struktury typu obiektu
Reference count = zliczenie odsyłaczy
type interface slots = szczeliny interfejsu typu
Dealloc Function = funkcja dealloc
Getattr Function = funkcja getattr
Setattr Function = funkcja setattr
tp_dealloc = tp_dealloc
tp_getattr = tp_getattr
tp_setattr = tp_setattr
tp_as_number = tp_jako_liczba
tp_as_sequence = tp_jako_ciąg
tp_as_mapping = tp_jako_odwzorowanie