O Obsłudze Wyjątków i Wyjątkach Słów Kilka
- Jarosław Piszczała
- 26 Mar, 2020
- 26 Mar, 2020
Jeden powiedziałby “jak ja nie cierpię tych wyjątków!” Inny rzekłby “wszystko by mi się udało, gdyby nie te wścibskie wyjątki i ten ich głupi traceback”. Aby zrozumieć, czym są wyjątki, dlaczego się pojawiają oraz jak je obsłużyć zagłębmy się w podstawach i omówmy wszystko, co jest z nimi związane.
Definicja
Wyjątek to mechanizm do obsługi niespodziewanych zdarzeń. Generowany jest, gdy pojawi się sytuacja nieprzewidziana oraz nieobsłużona przez programistę. Język Python ma zbiór wyjątków, które są przez niego zwracane w sytuacjach, które w świetle kodu nie powinny mieć miejsca. Ale wszystko po kolei.
Aby zobaczyć, co się dzieje, gdy zrobimy coś niespodziewanego, spróbujmy wygenerować błąd. Najprościej i najszybciej możemy to zrobić, dzieląc przez zero!
Generuje to tzw. traceback, czyli informacje z ostatniej transkacji - gdzie, i jaki wyjątek wystąpił. W tym przypadku wystąpił ZeroDivisionError
. Czy to jedyny wyjątek w Pythonie?
Drzewo wyjątków
Python w kwestii wyjątków korzysta z dziedziczenia. Głównym rodzicem wszystkich wyjątków jest BaseException
, po którym dziedziczą dwa kolejne wyjątki - Exception
który jest bazowym wyjątkiem wszystkich wyjątków, z których korzystamy w kodzie, oraz KeyboardInterrupt
który pomaga obsługiwać wszelkie skróty klawiszowe zatrzymujące pracę skryptu. Z tego też względu Nie możemy korzystać przy obsłudze wyjątków z wyjątku BaseException
, ponieważ użycie skrótów klawiszowych mogłoby być zignorowane.
Wracając do Exception
. Na powyższym obrazku możemy zauważyć, że pewne błędy zawierają się w sobie. Przypuśćmy, że nasza funkcja może wygenerować IndexError
lub KeyError
. Dzięki dziedziczeniu możemy przy obsłudze wyjątków skorzystać z ich rodzica, aby wyłapać obydwa wyjątki. Dość wygodne rozwiązanie.
Obsługa wyjątków
Ale jak obsługiwać wyjątki? Musimy skorzystać z klauzuli try except
. Część try
służy do zapisu kodu, który może wygenerować wyjątek. Przy instrukcji exception
definiujemy wyjątek, a następnie opisujemy procedurę do wykonania gdyby wyjątek się pojawił. W poniższym przypadku gdy wystąpi wyjątek, wyświetlimy użytkownikowi wiadomość, że wyjątek miał miejsce.
Except as
Przy obsłudze konkretnego wyjątku możemy także odwołać się do niego, aby wyświetlić informacje o nim, lub odpowiednio go obsłużyć w zależności od jego argumentów. Możemy dzięki temu, chociażby wygenerować odpowiedni komunikat, który zawierać będzie klasę wyjątku oraz jego treść.
Else
Czasem może się okazać, że kod, który miał wywołać wyjątek, nie wywołał go. Gdy potrzebujemy obsłużyć taki przypadek, stosujemy dodatkowo else
, w którym kod zostanie wywołany, gdy żaden wyjątek się nie pojawi. Dla przykładu gdy poprawimy nasz try
na poprawny kod, wyjątek się nie pojawi, więc otrzymamy komunikat z sekcji else
.
Finally
Nareszcie! Gdy wykonamy operację oraz obsłużymy wyjątki, oraz sytuacje jego braku, możemy zakończyć obsługę. Gdyby jednak okazało się, że potrzebujemy coś wykonać zawsze po tej obsłudze, skorzystać można z finally
. Kod w tej sekcji zostanie uruchomiony zawsze na zakończenie, nie można od jego egzekucji uciec. Przydaje się to gdy musimy zamknąć jakieś źródło, albo przesłać pewien log.
Obsługa wielu wyjątków
Mówiliśmy sobie o drzewie wyjątków. Nasza funkcja w zależności od przekazanych argumentów może generować całą listę wyjątków. Nic nie stoi na szczęście na przeszkodzie, aby każdy z nich odpowiednio obsłużyć! A więc, w try except
możemy obsługiwać wiele wyjątków poprzez dodanie kolejnych sekcji except
z kolejnym wyjątkiem do obsłużenia. Poniżej jeden z przykładów, gdy oczekiwane argumenty funkcji okazały się być innego typu niż int
.
Ponieważ powyższy przypadek obsługuje wyjątki w ten sam sposób, możemy także połączyć je. Z pomocą przychodzi tupla wyjątków, dzięki której pod jednym except
możemy obsłużyć kilka wyjątków jedną logiką:
Kolejność obsługi
Pamiętajmy jednak o jednym. Kolejność ma zawsze znaczenie. Tak samo jak w if elif else
kolejność wyrażeń logicznych jest ważna. Jeżeli wyżej przekażemy wyjątek wyższego rzędu, to on wyłapie wyjątek jako pierwszy, przed konkretnymi sekcjami oczekującymi na ten konkretny wyjątek. W tym wypadku nasz wyjątek zostanie wyłapany przez rodzica wszystkich wyjątków, czyli Exception
.
Wywołanie wyjątków
Tworząc nasz kod, nie zawsze możemy uniknąć wszystkich sytuacji. Czasem wręcz nie chcemy, aby użytkownik miał możliwość wykonać jakąś akcję. Aby okrzyczeć użytkownika, w stylu programisty Python, używamy instrukcji raise
. Pozwala ona wyrzucić wyjątek użytkownikowi.
Własny wyjątek
Nie każda sytuacja będzie miała dobry odpowiednik w Python. Są sytuacje gdy dużo lepiej pasowałby nasz własny wyjątek. Na szczęście wyjątki dziedziczą po sobie, a więc i my możemy stworzyć własny wyjątek! Najprostsza jego implementacja to stworzenie klasy dziedzicząca po klasie Exception
. Ze względu na to, że klasa musi coś posiadać w swoim ciele, dobrą praktyką jest dodanie docstringów.
Klasa bazowa wyjątków
Są pewne biblioteki, które generują masę różnych wyjątków. Głównie te powiązane z API. Aby użytkowników mógł je wszystkie na przykład zignorować, tworzymy dla modułu czy biblioteki wyjątek bazowy. Dzięki temu możemy wyłapywać wszystkie wyjątki danej biblioteki poprzez jeden jej bazowy wyjątek, bez wyłapywania wyjątków, chociażby tych wbudowanych Pythona.
Nie zapominajmy też, o obsłudze wyjątków zewnętrznych bibliotek w naszym kodzie. W ich przypadku należy wyjątki uprzednio zaimportować! Zdecydowanie nie powinniśmy ich obsługiwać, poprzez obsługę bazowego Exception
.
Pokemon Exception Handling
W ramach wyjątków czuję się odpowiedzialny, aby zwrócić Waszą uwagę na anty pattern związany z obsługą wyjątków. Nazwa Pokemon Exception Handling pochodzi ze świata pokemon, i dobrze opisuje ten pattern. “Pokemon - gotta catch ‘em all”. Zdecydowanie nie powinniśmy przy obsłudze wyłapywać wszystkich wyjątków, a następnie ich ignorować.
Jak widać, temat wyjątków jest duży, ale nie ma powodu do obaw. Zapamiętajmy z niego co najważniejsze, czyli jak obsługiwać wyjątki, jak poprawnie tworzyć własne wyjątki oraz w jaki sposób je wyrzucać. Nie ignorujmy tego tematu w naszym kodzie, oraz nie zapominajmy o tej możliwości. Wierzę, że od dziś spojrzycie na wyjątki bardziej przyjacielskim okiem!