1.2 Intermezzo: Błędy i wyjątki

Ważną konwencją stosowaną w interpreterze Pythona jest zapis informacji o wyjątku oraz zwracanie wartości oznaczającej błąd (najczęściej wskaźnik NULL). Wyjątki są realizowane za pomocą statycznej zmiennej globalnej w interpreterze Pythona. Jeśli zmienna ta ma wartość NULL, nie został wywołany wyjątek. Druga zmienna globalna przechowuje "przypisaną wartość" wyjątku (drugi argument wywołania instrukcji raise). Trzecia zmienna zawiera zrzut stosu wywołań, o ile wyjątek nastąpił w kodzie Pythona. Te trzy zmienne są odpowiednikami w języku C zmiennych: sys.exc_type, sys.exc_value and sys.exc_traceback (dalsze szczegóły znajdują się w opisie modułu sys w Opisie biblioteki Pythona). Aby zrozumieć zasadę propagacji błędów w programie należy zdawać sobie sprawę z istnienia tych zmiennych.

API Pythona definiuje kilka funkcji ułatwiających wywoływania różnych typów wyjątków.

Najbardziej powszechną z tych funkcji jest PyErr_SetString(). Jej argumenty stanowią obiekt wyjątku i napis w stylu C. Obiektem wyjątku najczęściej jest predefiniowany obiekt n.p. PyExc_ZeroDivisionError. Napis w stylu C opisuje przyczynę wystąpienia wyjątku i jest konwertowany na napis w stylu Pythona i przechowywany jako "wartość przypisana" wyjątku.

Kolejną użyteczną funkcją jest PyErr_SetFromErrno(), która przyjmuje tylko jeden argument - obiekt wyjątku, natomiast wartość przypisana zostaje skonstruowana poprzez analizę zmiennej globalnej errno. Najbardziej ogólną funkcją jest PyErr_SetObject(), która przyjmuje dwa argumenty - wyjątek i wartość przypisaną. Nie ma potrzeby wywoływania Py_INCREF() dla jakichkolwiek obiektów będących argumentami którejkolwiek z tych funkcji.

Niedestrukcyjne sprawdzenie wywołania wyjątku możliwe jest przy pomocy funkcji PyErr_Occurred(). Funkcja ta zwraca aktualny obiekt wyjątku lub NULL jeśli nie został wywołany żaden wyjątek. Zwykle nie ma potrzeby określania, czy nastąpiło wywołanie wyjątku przy pomocy funkcji PyErr_Occurred(), ponieważ wystarczy przeanalizować wartość zwracaną z funkcji.

Jeśli funkcja f wywołująca funkcję g stwierdzi błąd wykonania tej drugiej powinna zwrócić wartość oznaczającą błąd (zwykle NULL lub -1). Nie powinna wywoływać którejkolwiek z funkcji PyErr_*() -- odpowiednia z nich została już wywołana wewnątrz g. Procedura wywołująca funkcję f również powinna zwrócić wartość oznaczającą błąd bez wywoływania PyErr_*() i tak dalej -- najbardziej szczegółowy opis błędu został już zdefiniowany w funkcji, która pierwsza ten błąd wykryła. W momencie, gdy stos wywołań wróci do głównej pętli interpretera Pythona następuje wstrzymanie wykonania bieżącego kodu Pythona i interpreter podejmuje próbę odnalezienia procedury obsługi wyjątku zdefiniowanej przez programistę w kodzie Pythona.

(Istnieją sytuacje, gdy moduł może rzeczywiście uszczegółowić informację o wyjątku poprzez kolejne wywołanie jednej z funkcji PyErr_*() i w takiej sytuacji nie ma co do tego przeciwwskazań. Jednakże jako ogólną regułę należy przyjąć, że takie zachowanie nie jest niezbędne, co więcej może doprowadzić do utraty informacji o przyczynie błędu: większość operacji może zakończyć się błędem wskutek różnych przyczyn).

Aby zignorować wyjątek wywołany w funkcji, która zakończyła się niepowodzeniem zmienne definiujące wyjątek muszą zostać wyczyszczone poprzez wywołanie funkcji PyErr_Clear(). Jedynym przypadkiem, w którym powinno się wywoływać PyErr_Clear() jest chęć samodzielnego obsłużenia wyjątku przez funkcję (n.p. poprzez wypróbowanie innego rozwiązania lub ciche zignorowanie wystąpienia wyjątku) zamiast przekazywania go do funkcji wywołującej.

Należy również zauważyć, że za wyjątkiem PyArg_ParseTuple() i podobnych, funkcje zwracające stan wywołania w postaci liczby całkowitej zwykle zwracają liczbę dodatnią lub zero dla zasygnalizowania sukcesu lub liczbę -1 dla błędu, podobnie jak wywołania systemowe systemów Unix.

Ważne jest, aby wyczyścić śmieci powstałe wskutek wystąpienia błędu (poprzez wywołanie Py_XDECREF() lub Py_DECREF() na obiektach stworzonych w funkcji zwracającej kod błędu.

Wbudowane wyjątki Pythona, takie, jak PyExc_ZeroDivisionError, zostały zdefiniowane w języku C i mogą być wykorzystane bezpośrednio. Oczywiście należy dobierać wyjątki w sposób rozsądny -- nie należy używać PyExc_TypeError dla zasygnalizowania problemu z otwarciem pliku (najprawdopodobniej powinien zostać użyty PyExc_IOError). Jeśli coś jest nie w porządku z listą argumentów, funkcja PyArg_ParseTuple() wywołuje zwykle PyExc_TypeError. Jeśli wystąpi błąd argumentu, który powinien należeć do określonego zakresu lub spełniać inne warunki dotyczące wartości, właściwym wyjątkiem jest PyExc_ValueError.

Można również zdefiniować nowy wyjątek, unikalny dla tworzonego modułu. W tym celu zwykle deklaruje się obiekt statyczny na początku pliku:

static PyObject *SpamError;

który jest inicjowany jako obiekt wyjątku w funkcji initspam() inicjującej moduł (pomijamy na razie obsługę błędów):

PyMODINIT_FUNC
initspam(void)
{
    PyObject *m;

    m = Py_InitModule("spam", SpamMethods);

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_INCREF(SpamError);
    PyModule_AddObject(m, "error", SpamError);
}

Należy zwrócić uwagę na to, że nazwą obiektu wyjątku w Pythonie jest spam.error. Funkcja PyErr_NewException() może stworzyć klasę wyjątku z klasą bazową Exception (o ile inna klasa nie zostanie przekazana w parametrze w miejscu NULL) opisanej w Opisie biblioteki standardowej w rozdziale "Wbudowane wyjątki".

Godnym uwagi jest również fakt, że zmienna SpamError zachowuje referencję do nowo stworzonej klasy wyjątków. Taka funkcjonalność jest zamierzona! Z uwagi na możliwość usunięcia wyjątku z modułu przez zewnętrzny kod zachowana referencja do klasy zapewnia, że klasa nie zostanie usunięta powodując, że SpamError stanie się błędnym wskaźnikiem. W takiej sytuacji kod C wywołujący ten wyjątek powodowałby zrzucenie pliku core lub inne niepożądane efekty uboczne.

Wykorzystanie makra PyMODINIT_FUNC omówimy w dalszej części rozdziału.

Zajrzyj do Informacji na temat tej publikacji... aby pomóc w jej rozwoju.