❄️ Wyjątki
Wyjątki
Są to błędy, które występują podczas wykonywania programu i mogą powodować jego nagłe zakończenie, jeśli nie są odpowiednio obsłużone.
Zarządzanie wyjątkami, czyli kontrola nad tym, jak program zachowuje się w sytuacji, gdy wystąpi błąd, obejmuje wykorzystanie słów kluczowych try, except, else i finally oraz ręczne wywoływanie wyjątków za pomocą raise.
Podstawowa obsługa wyjątków
Podstawowy blok obsługi wyjątków składa się z try i except.
- W bloku
tryumieszczamy kod, który może wywołać wyjątek, - a blok
exceptprzechwytuje ten wyjątek i wykonuje odpowiednie działania.
try:
liczba = int(input("Podaj liczbę: "))
wynik = 10 / liczba
print(f"Wynik: {wynik}")
except ZeroDivisionError:
print("Nie można dzielić przez zero!")
except ValueError:
print("To nie jest poprawna liczba!")
Można obsłużyć wiele typów wyjątków osobno (jak wyżej) lub użyć bazowej klasy Exception, żeby złapać każdy błąd i przy okazji odczytać jego treść:
try:
liczba = int(input("Podaj liczbę: "))
wynik = 10 / liczba
print(f"Wynik: {wynik}")
except Exception as e:
print(f"Wystąpił błąd: {e}")
Dodatkowe bloki else i finally
- Kod w bloku
elsewykona się tylko wtedy, gdy żaden wyjątek nie wystąpił w blokutry. - Blok
finallywykona się zawsze, niezależnie od tego, czy wyjątek wystąpił. Używany głównie do zwalniania zasobów (zamykanie plików, połączeń itp.).
try:
liczba = int(input("Podaj liczbę: "))
wynik = 10 / liczba
except ZeroDivisionError:
print("Nie można dzielić przez zero!")
except ValueError:
print("To nie jest poprawna liczba!")
else:
print(f"Wynik: {wynik}")
finally:
print("Blok finally wykonuje się zawsze.")
Ręczne wywoływanie wyjątków - raise
Funkcje powinny aktywnie zgłaszać wyjątki, gdy otrzymują niepoprawne dane - zamiast cicho zwracać błędne wyniki. To właśnie widziałeś już w sekcji o docstringach (raise ValueError).
def sprawdz_wiek(wiek):
if wiek < 18:
raise ValueError("Wiek musi być co najmniej 18 lat.")
return "Dostęp dozwolony"
try:
print(sprawdz_wiek(15))
except ValueError as e:
print(f"Błąd: {e}")
Większość wyjątków to klasy wbudowane dostępne w standardowej bibliotece - pełna lista w dokumentacji.
Najczęściej wykorzystywane wyjątki
ValueError- argument ma poprawny typ, ale niepoprawną wartość.TypeError- argument jest niepoprawnego typu.IndexError- indeks jest poza zakresem listy, krotki itp.KeyError- klucz nie istnieje w słowniku.AttributeError- obiekt nie ma określonego atrybutu.FileNotFoundError- określony plik nie istnieje.ZeroDivisionError- próba dzielenia przez zero.PermissionError- brak uprawnień do wykonania operacji.RuntimeError- wyjątek ogólny, gdy żaden inny nie pasuje.NotImplementedError- metoda zamierzona do implementacji w przyszłości.OverflowError- wynik operacji matematycznej zbyt duży do przetworzenia.ImportError- nie udało się zaimportować modułu lub elementu.AssertionError- sprawdzenieassertzakończyło się niepowodzeniem.
Tworzenie własnych wyjątków
Wymaga znajomości klas
Ta sekcja korzysta z dziedziczenia klas - mechanizmu omawianego w późniejszych zajęciach z programowania obiektowego. Możesz ją teraz przejrzeć poglądowo i wrócić po przerobieniu OOP.
Własne wyjątki tworzy się przez zdefiniowanie nowej klasy dziedziczącej po Exception. Pozwala to tworzyć błędy dopasowane do konkretnego kontekstu aplikacji.
Prosty przykład
class BrakSrodkowError(Exception):
"""Wyjątek sygnalizujący brak wystarczających środków na koncie."""
pass
def wyplac(saldo, kwota):
if kwota > saldo:
raise BrakSrodkowError(
f"Brak środków: saldo {saldo} zł, potrzebne {kwota} zł"
)
return saldo - kwota
try:
wyplac(100, 150)
except BrakSrodkowError as e:
print(f"Błąd: {e}")
Bardziej rozbudowany przykład z atrybutami
class BladZakresu(Exception):
def __init__(self, wartosc, zakres_min, zakres_max):
super().__init__(
f"Wartość {wartosc} jest poza zakresem ({zakres_min}-{zakres_max})."
)
self.wartosc = wartosc
self.zakres_min = zakres_min
self.zakres_max = zakres_max
def sprawdz_zakres(wartosc):
if not (0 <= wartosc <= 100):
raise BladZakresu(wartosc, 0, 100)
try:
sprawdz_zakres(150)
except BladZakresu as e:
print(f"Błąd: {e}")
print(f"Wartość: {e.wartosc}, zakres: {e.zakres_min}-{e.zakres_max}")
Hierarchia wyjątków
Własne wyjątki można układać w hierarchię - ogólna klasa bazowa i bardziej szczegółowe klasy pochodne:
class BladAplikacji(Exception):
pass
class BladPolaczenia(BladAplikacji):
pass
class BladAutoryzacji(BladAplikacji):
pass
try:
raise BladPolaczenia("Błąd podczas łączenia z serwerem.")
except BladAplikacji as e:
print(f"Błąd aplikacji: {e}") # łapie oba typy pochodne
Zapis except Exception as e
except Exception as e przechwytuje wszystkie wyjątki, bo wszystkie wbudowane wyjątki dziedziczą po Exception. Dlatego własne wyjątki też powinny po niej dziedziczyć.
📝 Zadania
Stwórz plik python1course/zaj02/wyjatki.py i wykonaj w nim poniższe zadania.
-
Napisz funkcję
konwertuj_na_int(tekst), która próbuje zamienić podany ciąg znaków na liczbę całkowitą. Jeśli się nie uda, powinna wypisać czytelny komunikat o błędzie i zwrócićNone. Przetestuj ją na kilku przypadkach:"42","abc","3.14","". -
Napisz funkcję
oblicz_bmi(masa, wzrost), która:- Zgłasza
TypeError, jeśli któryś z argumentów nie jest liczbą, - Zgłasza
ValueError, jeśli masa ≤ 0 lub wzrost ≤ 0, - W pozostałych przypadkach zwraca wynik (
masa / wzrost ** 2).
Napisz kod wywołujący, który testuje wszystkie przypadki błędów z użyciem
try/except. - Zgłasza
-
Napisz funkcję
wczytaj_plik(sciezka), która otwiera plik tekstowy i zwraca jego zawartość jako string. Użyj blokuwith open(). Obsłuż wyjątkiFileNotFoundErroriPermissionError- w obu przypadkach wypisz czytelny komunikat i zwróćNone. -
Napisz funkcję
polacz_z_baza(host), która symuluje nawiązywanie połączenia z bazą danych. Użyj blokufinally, żeby zawsze wypisać komunikat o zakończeniu próby połączenia - niezależnie od tego, czy się powiodła. Zasymuluj błąd dla wybranych hostów (np.raise ConnectionErrorgdyhost == "zly-host").Kiedy
finallyma sens?withrozwiązuje problem zamykania plików i innych zasobów obsługujących protokół kontekstu.finallyprzydaje się wszędzie tam, gdzie tego protokołu nie ma - np. połączenia sieciowe, logi, liczniki prób, zwalnianie blokad. -
Napisz funkcję
pobierz_wartosc(slownik, klucz), która zwraca wartość ze słownika dla podanego klucza. Jeśli klucz nie istnieje, nie powinna rzucićKeyError- zamiast tego niech zgłosiValueErrorz komunikatem wskazującym, jakiego klucza brakuje i jakie klucze są dostępne.