2024-12-14

Myśląc o Pythonie: Błędy i wyjątki

Opowiedzmy sobie o dość podstaowej rzeczy. Jako ludzie, nie jesteśmy wcale doskonali i popełniamy błędy, coś czasem przeoczymy, coś nam nie do końca wyjdzie. Błądzić i mylić się ludzka rzecz, a programiści jak się okazuje to też ludzie. Zatem wymyślili coś by radzić sobie z błędami, czy tzw. wyjątkami. Porozmawiajmy zatem co to są błędy i wyjątki, i jak się z nimi obchodzić.

W świecie programowania, błędy są czymś absolutnie naturalnym i nieuniknionym. Każdy, kto kiedykolwiek napisał choćby kilka linijek kodu, wie, że błędy są nieodłącznym elementem procesu tworzenia oprogramowania. Mogą wynikać z ludzkiej pomyłki, złożoności projektu, nieprzewidzianych warunków lub niewłaściwego założenia na etapie planowania. Jednak zamiast traktować błędy jako porażki, powinniśmy widzieć w nich okazję do nauki i doskonalenia naszych umiejętności. Programowanie to sztuka, w której iteracja, poprawki i nauka na błędach są kluczowymi elementami sukcesu. Dlatego umiejętność radzenia sobie z błędami i wyjątkami jest jednym z fundamentów bycia skutecznym programistą.

Rodzaje błędów

W życiu błędy mogą być różnego rodzaju i typu, możemy popełnić błąd przy wysyłaniu przelewu, ale może nam się też pomylić adres pod którym mamy spotkanie. Podobnie jest w programowaniu, błędy możemy podzielić na różne typy. Przykładowo błąd składniowy - nie domknęliśmy łańcucha znaków czy nawiasu. Po prostu w programie pojawia się jakiś nieprawidłowy element i podczas wykonywania skutkuje to błędem. W takiej sytuacji program przerywa swoje działanie, bo nie jest w stanie się wykonać. Jeśli chodzi o błędy składniowe, to zwykle to interpreter, albo już nawet sam edytor podpowiada nam gdzie i jakiego typu mamy taki błąd.

Mamy też błędy logiczne, gdy nasz program nie działa tak jak się spodziewaliśmy. Błędy takie są trudniejsze do wyłapania, bo program jest napisany poprawnie składniowo, więc sam interpreter czy edytor nie muszą zwrócić nam żadnego błędu. Jakieś wartości mogą być nieprawidłowo przekazywane, coś może być niespodziewanie modyfikowane czy jakiś obiekt może nie istnieć, a my akurat go potrzebujemy. Takiego typu błędy są trudne do wyśledzenia i wymagają przeanalizowania programu często krok po kroku.

Kolejny z typów błędów zwany jest runtime errors. Skutkiem tego rodzaju błędów, w przeciwieństwie do np logicznych, jest przerwanie działania programu. Błędy te zazwyczaj wynikają z nieprawidłowych wartości, przykładowym przypadkiem jest dzielenie przez zero. Nieprawidłowa wartość na wejściu, przy dzieleniu, skutkuje pojawianiem się błędu zgłaszanego często jeko zero devision error. Innym przykładem może być próba połączenia niekompatybilnych ze sobą danych. Dodanie napisu do liczby, to zwykle błąd kategorii runtime error.

Obsługa błędów i wyjątków

Python zapewnia "out of the box" obsługę podstawowych rodzajów błędów, ale oczywiście możemy też wprowadzać własne mechanizmy obsługi , czyli radzenia sobie z błędami. Cały cel polega na tym by zidentyfikować błąd, zlokalizować go i być świadomym co można z nim zrobić.

Zobaczmy podstwowy przykład. Spróbujmy wyświetlić zmienną x, której wcześniej nie zadeklarowaliśmy. Python raczej nie powinien wiedzieć czym jest takie x.

błąd przy braku interpretera Pythona w podanej scieżce.

Jak możemy zauważyć otrzymaliśmy dużo czerwonego tekstu. Komunikat mówi, że system nie może znaleźć ścieżki. I to akurat moja wina, bo mój edytor przestał widzieć Pythona, więc nie może go uruchomić. Poniżej mamy już adekwatny do przykładu komunikat.

błąd przy wyświetleniu niezdefiniowanej zmiennej w języku Python - obsługa błędów i wyjątków

Komunikat błędu, gdy nie zdefiniujemy zmiennej jakiej chemy użyć, mówi nam, że 'x' jest niezdefiniowane. Jest to błąd typu NameError. No i oczywiście jest to racja, nie definiowaliśmy żadnego x. Więc skąd interpreter ma wiedzieć o czym my mówimy i czym jest to tajemnicze x.

Błędy i wyjątki - try ... except

Gdybyśmy chcieli zabezpieczyć się przed takim błędem jak właśnie przykładowo ZeroDivisionError moglibyśmy spróbowac użyć ifa. Jednak błąd przerwałby wykonywanie kodu. Do takich zadań mamy specjalną konsstrukcję try ... except. Która dosłownie "spróbuje" coś wykonać i jeśli się nie uda to coś zrobi. Zobaczmy przykład.

błędy i wyjątki - zwykły ZeroDivisionError w języku Python

Jak widzimy na razie bez żadnego zaskoczenia otrzymujemy błąd, ze wskazaniem gdzie występuje.

try except i ogólny wyjątek w Pythonie

Ciekawiej robi się gdy zastosujemy blok try i umieścimy w nim kod, który może wywołać błąd. Następnie zastosuemy blok except i określimy Exception (czyli obiekt ogólny wyjątków). W bloku którym możemy określić co się dzieje, gdy Exception wystąpi. Innymi słowy zyskaliśmy kontrolę nad tym co się dzieje gdy nasze dzielenie spowoduje błąd.

Oczywiście słówko Exception określa ogólnie jakiś wyjątek, moglibyśmy tam też zastosować inne słowo. Inną nazwę błędu przykładowo ZeroDivisionError. W takim wypadku ograniczamy się tylko do działania tylko gdy wystąpi error typu ZeroDivisionError.

błędy i wyjątki w języku Python - dokładne określenie typu błędu w try except

Zatem używając bloku try .. except mówimy niejako 'spróbuj wykonać to co jest w tym bloku (try), z wyjątkiem wystąpienia takiego błędu (podajemy rodzaj błędu), wtedy zrób to -podajemy co zrobić w bloku except)".

Teraz, gdy już określiśmy rodzaj błędu, jakiego się spodziewamy może pojawić się problem. Mianowicie, co jeśli wystąpi inny błąd? Zobaczmy, zmieńmy na chwilę dzielenie na dodawanie int i str.

No i klops, mamy inny typ błędu. Teraz wystepuje TypeError, a nie ZeroDivisionError, więc nasz blok except nic nie zauważył. Dla niego nie ma innych typów niż jego ukochany ZeroDivisionError. Na całe szczęście my nie panikujemy. Mamy do dyspozycji kolejny trick, w postaci... Kolejnego bloku except. Tak, możemy najzwyczajniej w świecie dodać sobie następny except.

Teraz gdy będziemy mieli błąd TypeError, to i tak go przechwycimy i wyprintujemy ładny komunikat dla użytkownika 🙂

Instrukcja rise

W naszym arsenale programistów, blok try .. except nie jest jedynym orężem. Dodatkowo do dyspozycji przychodzi nam też słówko rise, które pozwoli "wyrzucić", albo zwrócić własny typ błędu. Przykładowo możemy w jakiejś sytuacji zwrócić własny typ błędu, który zostanie gdzieś niżej w kodzie rozpoznany. Jeśli taki błąd wystąpi, to zaniechamy pierwotnej operacji. Stwórzmy sobie przykład prostej funkcji. Pamiętasz jeszcze jak je tworzyć?

Myślę, że doskonale wiesz, że ta funkcja po prostu dzieli dwie liczby przez siebie i zwraca wynik. Podówjny ukośnik to po prostu dzielenie całkowite, czyli tzw. integer division. Jest to nic innego jak wymuszenie by wynik nie zawierał części po przecinku.

Jeśli jako drugi argument funkcji divide podalibyśmy zero, będziemy mieć znowu ZeroDivisionError. Moglibyśmy teraz sprawdzić czy zmienna b ma wartość zero i w takim wypadku zwrócić własny błąd. Zróbmy tak. Zatem sprawdzimy czy b jest zerem, a jeśli tak to za pomocą słówka rise zwrócimy błąd.

użycie słowa kluczowego raise do zwrócenia swojego błędu w języku Python

Mamy zatem sprawdzenie zmiennej b, a jeśli warunek zostanie spełniony, to zwracamy błąd. Słowo raise wymaga jeszcze użycia rodzaju błędu, bo każdy błąd musi mieć jakiś typ. W Pythonie istnieją już wbudowane klasy błędów i tutaj musimy właśnie jakiejś użyć. Ja użyłem ValueError z komunikatem "Cannot divide by zero."

Try .. except i raise w słóżbie błędów i wyjątków

Następnie, po uruchomieniu, rzeczywiście widzimy błąd Cannot divide by zero. Mamy jednak mały problem, bo widzimy też zwykły ogólny błąd interpretera. Nie jest to najładniejszy widok, a dla użytkownika może być przerażający. Dzieje się tak, ponieważ prócz zrócenia błędu musimy go jeszcze przechwycić. Tutaj właśnie używamy try .. except. Innymi słowy, stworzyliśmy teraz swój błąd i musimy użyć mechanizmu, który zauważy, że on wystąpił i zareaguje tak jak chcemy.

Użyliśmy zatem try .. except tak jak omówiliśmy poniżej i.... no i mamy błąd NameError. Dlaczego, skoro powinien być ValueError przechwycony i ładnie wyświetlony. No właśnie, jest tutaj jeden haczyk. Ponieważ gdy sprawdzamy, czy b jest równe zero, wyrzucamy w przypadku spełnienia warunku. Taki działanie powoduje przerwanie działania funkcji. Prowadzi to zatem do braku przypisania, więc i utworzenia zmiennej res. Dlatego takiej zmiennej nie ma, co interpreter Nam właśnie tutaj mówi.

Rozwiązaniem mogłoby być po prostu wyświetlenie tego co dostaniemy z funkcji bezpośrednio. W takim wypadku nie odwołujemy się na końcu do zmiennej, gdzie nie mamy pewności czy ona istnieje. Po prostu jeśli wystąpi błąd to nie wyświetlimy wyniku funkcji, a jeśli nie wystąpi to wyświetlimy.

Jaki zatem będzie wynik powyższego kodu? Sprawdź sam 😉

Czy znasz inne sposoby na obejście tego problemu?

Blok finally

Do bloku try ... except możemy również dodać sekcję finally, która wykona się zawsze, niezależnie od tego, czy wystąpił błąd, czy nie. Jest to użyteczne, gdy musimy posprzątać po jakiejś operacji, np. zamknąć plik lub połączenie z bazą danych. Zostawiam ci tą kwestię do przetestowania w domu, dorgi czytelniku 😉

Dodaj komentarz

linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram