15. Python – obsługa błędów i wyjątków


Szybki quiz – przypomnienie poprzedniej lekcji

Temat poprzedniej lekcji: operacje na plikach.

1. Plik raport.txt już istnieje i zawiera dane. Co się z nim stanie po wykonaniu poniższego kodu?

with open("raport.txt", "w", encoding="utf-8") as plik:
    plik.write("Nowy raport")

2. Co zostanie wyświetlone?

with open("a.txt", "w", encoding="utf-8") as f:
    f.write("Ala")
    f.write("Ola")

with open("a.txt", "a", encoding="utf-8") as f:
    f.write("\nKot")

with open("a.txt", "r", encoding="utf-8") as f:
    print(f.read())

3. Znajdź błąd w kodzie. Dlaczego ostatnia linia nie zadziała?

with open("dane.txt", "r", encoding="utf-8") as plik:
    zawartosc = plik.read()

print(plik.read())

4. Uzupełnij kod tak, aby wypisał plik linia po linii, bez wczytywania całej zawartości do pamięci:

with open("dane.txt", "r", encoding="utf-8") as plik:
    for ____ in ____:
        print(linia.strip())

5. Który tryb otwarcia pliku: a) dopisuje na końcu i tworzy plik, jeśli go nie ma, b) zgłasza błąd, jeśli plik już istnieje?

6. Podaj dwa sposoby zabezpieczenia programu przed brakiem pliku, które pokazano w poprzedniej lekcji.

Odpowiedzi
  1. Dotychczasowa zawartość zostanie nadpisana (tryb 'w'). W pliku będzie tylko Nowy raport.
  2. Wyświetli się:
   AlaOla
   Kot

write() nie dodaje znaku nowej linii sam z siebie – AlaOla jest w jednej linii. Dopiero \n w trybie 'a' przeniósł Kot do nowej linii.
3. Po wyjściu z bloku with plik jest automatycznie zamknięty, więc plik.read() poza blokiem zgłosi błąd (ValueError: I/O operation on closed file). Poprawnie: print(zawartosc).
4. for linia in plik:
5. a) 'a', b) 'x'.
6. try / except FileNotFoundError oraz sprawdzenie os.path.exists(nazwa_pliku) w instrukcji if.


Błędy (errors) i wyjątki (exceptions) to sytuacje, które przerywają normalne wykonanie programu. Bez odpowiedniej obsługi program po prostu się „wysypie” i zakończy działanie.

Dlaczego ważna jest obsługa błędów?

  • Program nie kończy działania przy błędzie
  • Możesz reagować na błędy (np. prosić użytkownika o poprawne dane)
  • Lepsze doświadczenie użytkownika
  • Łatwiejsze debugowanie
  • Profesjonalny kod produkcyjny

Przykład bez obsługi błędu:

# Program się wysypie jeśli plik nie istnieje
with open("nieistniejacy.txt") as file:
    content = file.read()
# FileNotFoundError - program kończy działanie

Z obsługą błędu:

# Program kontynuuje działanie
try:
    with open("nieistniejacy.txt") as file:
        content = file.read()
except FileNotFoundError:
    print("Plik nie istnieje, tworzę nowy")
    with open("nieistniejacy.txt", "w") as file:
        file.write("Nowa zawartość")

Struktura try-except

Podstawowa składnia

try:
    # Kod, który może wywołać błąd
    ryzykowna_operacja()
except:
    # Kod wykonywany gdy wystąpi błąd
    obsluga_bledu()

Pełna struktura

try:
    # Spróbuj wykonać ten kod
    kod_ktory_moze_wywolac_blad()
except TypBledu:
    # Wykonaj gdy wystąpi konkretny błąd
    obsluga_bledu()
else:
    # Wykonaj TYLKO jeśli NIE było błędu
    kod_gdy_sukces()
finally:
    # Wykonaj ZAWSZE (bez względu na błędy)
    sprzatanie()

Kolejność:

  1. try – zawsze jako pierwsze
  2. except – jeden lub więcej
  3. else – opcjonalnie, tylko jeśli nie było błędu
  4. finally – opcjonalnie, wykonuje się zawsze

Podstawowe typy błędów

FileNotFoundError – brak pliku

# Błąd: próba otwarcia nieistniejącego pliku
with open("nieistniejacy.txt") as file:
    content = file.read()
# FileNotFoundError: [Errno 2] No such file or directory: 'nieistniejacy.txt'

Obsługa:

try:
    with open("nieistniejacy.txt") as file:
        content = file.read()
except FileNotFoundError:
    print("Plik nie istnieje!")

KeyError – brak klucza w słowniku

# Błąd: próba dostępu do nieistniejącego klucza
slownik = {"imie": "Jan", "wiek": 25}
miasto = slownik["miasto"]
# KeyError: 'miasto'

Obsługa:

slownik = {"imie": "Jan", "wiek": 25}

try:
    miasto = slownik["miasto"]
except KeyError:
    print("Klucz 'miasto' nie istnieje")
    miasto = "Nieznane"

IndexError – indeks poza zakresem

# Błąd: próba dostępu do nieistniejącego indeksu
lista = ["Python", "Java", "C++"]
jezyk = lista[5]
# IndexError: list index out of range

Obsługa:

lista = ["Python", "Java", "C++"]

try:
    jezyk = lista[5]
except IndexError:
    print("Indeks poza zakresem listy")

ValueError – nieprawidłowa wartość

# Błąd: konwersja niepoprawnej wartości
wiek = int("dwadzieścia")
# ValueError: invalid literal for int() with base 10: 'dwadzieścia'

Obsługa:

try:
    wiek = int(input("Podaj wiek: "))
except ValueError:
    print("To nie jest liczba!")
    wiek = 0

TypeError – nieprawidłowy typ

# Błąd: dodawanie string + int
tekst = "Python"
wynik = tekst + 5
# TypeError: can only concatenate str (not "int") to str

Obsługa:

tekst = "Python"

try:
    wynik = tekst + 5
except TypeError:
    print("Nie można dodać liczby do tekstu")
    wynik = tekst + str(5)

ZeroDivisionError – dzielenie przez zero

# Błąd: dzielenie przez 0
wynik = 10 / 0
# ZeroDivisionError: division by zero

Obsługa:

try:
    wynik = 10 / 0
except ZeroDivisionError:
    print("Nie można dzielić przez zero!")
    wynik = None

Przechwytywanie konkretnych błędów

ŹLE – ogólny except

# ŹLE - łapie WSZYSTKIE błędy
try:
    with open("plik.txt") as file:
        content = file.read()
    slownik = {"klucz": "wartość"}
    print(slownik["nieistniejacy"])
except:
    print("Jakiś błąd")  # Który? Nie wiadomo!

Problemy:

  • Nie wiesz jaki błąd wystąpił
  • Trudno debugować
  • Możesz ukryć poważne błędy
  • Nie możesz różnie reagować na różne błędy

DOBRZE – konkretne except

# DOBRZE - konkretne błędy
try:
    with open("plik.txt") as file:
        content = file.read()
    slownik = {"klucz": "wartość"}
    print(slownik["nieistniejacy"])
except FileNotFoundError:
    print("Plik nie istnieje")
except KeyError as e:
    print(f"Klucz {e} nie istnieje w słowniku")

Wiele except

Możesz mieć wiele bloków except dla różnych błędów.

try:
    liczba = int(input("Podaj liczbę: "))
    wynik = 100 / liczba
    print(f"Wynik: {wynik}")
except ValueError:
    print("To nie jest liczba!")
except ZeroDivisionError:
    print("Nie można dzielić przez zero!")
except KeyboardInterrupt:
    print("\nPrzerwano przez użytkownika")

Przypisanie błędu do zmiennej

try:
    slownik = {"imie": "Jan"}
    print(slownik["nazwisko"])
except KeyError as error:
    print(f"Błąd: brak klucza {error}")
    # Błąd: brak klucza 'nazwisko'

Inne przykłady:

try:
    plik = open("nieistniejacy.txt")
except FileNotFoundError as e:
    print(f"Szczegóły błędu: {e}")

try:
    wynik = int("abc")
except ValueError as e:
    print(f"Błąd konwersji: {e}")

Jeden except dla wielu błędów

# Obsługa wielu błędów tym samym kodem
try:
    # Kod ryzykowny
    operacja()
except (ValueError, TypeError, KeyError) as e:
    print(f"Wystąpił błąd: {e}")

Blok else

Blok else wykonuje się TYLKO gdy w try NIE było błędu.

try:
    with open("plik.txt") as file:
        content = file.read()
except FileNotFoundError:
    print("Plik nie istnieje")
else:
    # Wykonuje się TYLKO gdy plik został poprawnie otwarty
    print("Zawartość pliku:")
    print(content)

Przykład – walidacja danych

try:
    wiek = int(input("Podaj wiek: "))
except ValueError:
    print("To nie jest liczba!")
else:
    # Wykonuje się tylko gdy konwersja się powiodła
    if wiek >= 18:
        print("Jesteś pełnoletni")
    else:
        print("Jesteś niepełnoletni")

Blok finally

Blok finally wykonuje się ZAWSZE, niezależnie czy był błąd czy nie.

try:
    plik = open("dane.txt", "w")
    plik.write("Dane")
except IOError:
    print("Błąd zapisu")
else:
    print("Zapis udany")
finally:
    # ZAWSZE zamknij plik (czy był błąd czy nie)
    plik.close()
    print("Plik zamknięty")

Typowe zastosowania finally:

  • Zamykanie plików
  • Zamykanie połączeń z bazą danych
  • Zwalnianie zasobów
  • Sprzątanie po operacji

Przykład – finally zawsze się wykona

try:
    print("Try")
    raise ValueError("Błąd!")
except ValueError:
    print("Except - złapano błąd")
else:
    print("Else - nie wykona się (był błąd)")
finally:
    print("Finally - zawsze się wykona")

Wynik:

Try
Except - złapano błąd
Finally - zawsze się wykona

Wywoływanie własnych błędów – raise

Możesz ręcznie wywołać błąd używając słowa kluczowego raise.

Podstawowa składnia

raise TypBledu("Komunikat błędu")

Przykład – walidacja wzrostu

def oblicz_bmi(waga, wzrost):
    """Oblicza BMI z walidacją danych."""
    
    if wzrost > 3:
        raise ValueError("Wzrost człowieka nie przekracza 3 metrów!")
    
    if wzrost <= 0:
        raise ValueError("Wzrost musi być większy od 0!")
    
    if waga <= 0:
        raise ValueError("Waga musi być większa od 0!")
    
    bmi = waga / (wzrost ** 2)
    return bmi

# Użycie
try:
    wynik = oblicz_bmi(70, 3.5)
except ValueError as e:
    print(f"Błąd: {e}")
    # Błąd: Wzrost człowieka nie przekracza 3 metrów!

Przykład – funkcja z walidacją

def podziel(a, b):
    """Dzieli dwie liczby z walidacją."""
    
    if not isinstance(a, (int, float)):
        raise TypeError("Pierwszy argument musi być liczbą")
    
    if not isinstance(b, (int, float)):
        raise TypeError("Drugi argument musi być liczbą")
    
    if b == 0:
        raise ZeroDivisionError("Nie można dzielić przez zero")
    
    return a / b

# Użycie
try:
    wynik = podziel(10, 0)
except ZeroDivisionError as e:
    print(f"Błąd: {e}")

Re-raise – ponowne wywołanie błędu

try:
    wynik = 10 / 0
except ZeroDivisionError:
    print("Logowanie błędu...")
    raise  # Ponownie wywołaj ten sam błąd

Przykłady praktyczne

Przykład 1: Bezpieczny input liczby

def pobierz_liczbe(komunikat):
    """Pobiera liczbę od użytkownika z walidacją."""
    while True:
        try:
            liczba = float(input(komunikat))
            return liczba
        except ValueError:
            print("To nie jest liczba! Spróbuj ponownie.")

# Użycie
wiek = pobierz_liczbe("Podaj wiek: ")
print(f"Twój wiek: {wiek}")

Przykład 2: Kalkulator z obsługą błędów

def kalkulator():
    """Prosty kalkulator z obsługą błędów."""
    try:
        a = float(input("Pierwsza liczba: "))
        b = float(input("Druga liczba: "))
        operator = input("Operator (+, -, *, /): ")
        
        if operator == "+":
            wynik = a + b
        elif operator == "-":
            wynik = a - b
        elif operator == "*":
            wynik = a * b
        elif operator == "/":
            if b == 0:
                raise ZeroDivisionError("Nie można dzielić przez zero!")
            wynik = a / b
        else:
            raise ValueError(f"Nieznany operator: {operator}")
        
        print(f"Wynik: {wynik}")
        
    except ValueError as e:
        print(f"Błąd wartości: {e}")
    except ZeroDivisionError as e:
        print(f"Błąd dzielenia: {e}")
    except Exception as e:
        print(f"Nieoczekiwany błąd: {e}")

kalkulator()

Przykład 3: Bezpieczne otwieranie pliku

def wczytaj_plik(nazwa):
    """Bezpiecznie wczytuje plik."""
    try:
        with open(nazwa, 'r', encoding='utf-8') as plik:
            return plik.read()
    except FileNotFoundError:
        print(f"Plik {nazwa} nie istnieje")
        return None
    except PermissionError:
        print(f"Brak uprawnień do pliku {nazwa}")
        return None
    except Exception as e:
        print(f"Nieoczekiwany błąd: {e}")
        return None

# Użycie
zawartosc = wczytaj_plik("dane.txt")
if zawartosc:
    print(zawartosc)

Przykład 4: Słownik z domyślnymi wartościami

def pobierz_wartosc(slownik, klucz, domyslna=None):
    """Pobiera wartość ze słownika z obsługą błędów."""
    try:
        return slownik[klucz]
    except KeyError:
        print(f"Klucz '{klucz}' nie istnieje, zwracam wartość domyślną")
        return domyslna

# Użycie
dane = {"imie": "Jan", "wiek": 25}
miasto = pobierz_wartosc(dane, "miasto", "Warszawa")
print(f"Miasto: {miasto}")  # Miasto: Warszawa

Przykład 5: Lista z bezpiecznym dostępem

def bezpieczny_dostep(lista, indeks):
    """Bezpieczny dostęp do elementu listy."""
    try:
        return lista[indeks]
    except IndexError:
        print(f"Indeks {indeks} poza zakresem (0-{len(lista)-1})")
        return None
    except TypeError:
        print("Lista jest None lub indeks nie jest liczbą")
        return None

# Użycie
owoce = ["jabłko", "banan", "gruszka"]
owoc = bezpieczny_dostep(owoce, 5)
print(owoc)  # None

Przykład 6: Przechwyć błędy w funkcji make_pie

Problem:

fruits = ["Apple", "Pear", "Orange"]

def make_pie(index):
    fruit = fruits[index]
    print(fruit + " pie")

make_pie(4)  # IndexError!

Rozwiązanie:

fruits = ["Apple", "Pear", "Orange"]

def make_pie(index):
    try:
        fruit = fruits[index]
    except IndexError:
        print(f"Nie mamy takiego składnika. Wybierz 0-{len(fruits)-1}")
    else:
        print(fruit + " pie")

make_pie(4)  # Nie mamy takiego składnika. Wybierz 0-2
make_pie(1)  # Pear pie

Przykład 7: Zapobiegnij błędom w postach

Problem:

facebook_posts = [
    {'Likes': 21, 'Comments': 2},
    {'Likes': 13, 'Comments': 2, 'Shares': 1},
    {'Likes': 33, 'Comments': 8, 'Shares': 3},
    {'Comments': 4, 'Shares': 2},  # Brak 'Likes'
    {'Comments': 1, 'Shares': 1},  # Brak 'Likes'
    {'Likes': 19, 'Comments': 3}
]

total_likes = 0
for post in facebook_posts:
    total_likes = total_likes + post['Likes']  # KeyError!

Rozwiązanie:

facebook_posts = [
    {'Likes': 21, 'Comments': 2},
    {'Likes': 13, 'Comments': 2, 'Shares': 1},
    {'Likes': 33, 'Comments': 8, 'Shares': 3},
    {'Comments': 4, 'Shares': 2},
    {'Comments': 1, 'Shares': 1},
    {'Likes': 19, 'Comments': 3}
]

total_likes = 0

for post in facebook_posts:
    try:
        total_likes += post['Likes']
    except KeyError:
        # Post nie ma 'Likes', pomijamy
        pass

print(f"Łączna liczba polubień: {total_likes}")
# Łączna liczba polubień: 86

Alternatywne rozwiązanie (bez try-except):

total_likes = 0
for post in facebook_posts:
    total_likes += post.get('Likes', 0)
print(f"Łączna liczba polubień: {total_likes}")

Przykład 8: Program dzielący liczby

Wymagania:

  • Pobierz dwie liczby
  • Podziel pierwszą przez drugą
  • Obsłuż: zły format, dzielenie przez 0, inne błędy

Rozwiązanie:

def dziel_liczby():
    """Program dzielący dwie liczby z pełną obsługą błędów."""
    try:
        # Pobranie liczb
        a = float(input("Podaj pierwszą liczbę: "))
        b = float(input("Podaj drugą liczbę: "))
        
        # Dzielenie
        wynik = a / b
        
    except ValueError:
        print("Błąd: Podano nieprawidłowy format liczby!")
    except ZeroDivisionError:
        print("Błąd: Nie można dzielić przez zero!")
    except KeyboardInterrupt:
        print("\nProgram przerwany przez użytkownika")
    except Exception as e:
        print(f"Nieoczekiwany błąd: {e}")
    else:
        print(f"Wynik dzielenia: {wynik:.2f}")
    finally:
        print("Koniec operacji")

# Uruchomienie
dziel_liczby()

Ulepszona wersja z pętlą:

def dziel_liczby_petla():
    """Wersja z ponawianiem przy błędach."""
    while True:
        try:
            a = float(input("\nPodaj pierwszą liczbę (lub 'q' aby wyjść): "))
            b = float(input("Podaj drugą liczbę: "))
            
            if b == 0:
                raise ZeroDivisionError("Dzielenie przez zero!")
            
            wynik = a / b
            print(f"Wynik: {a} / {b} = {wynik:.2f}")
            break
            
        except ValueError:
            odpowiedz = input("Nieprawidłowy format! Spróbować ponownie? (t/n): ")
            if odpowiedz.lower() != 't':
                break
        except ZeroDivisionError as e:
            print(f"Błąd: {e}")
            odpowiedz = input("Spróbować ponownie? (t/n): ")
            if odpowiedz.lower() != 't':
                break

dziel_liczby_petla()

Tworzenie własnych klas wyjątków

Możesz tworzyć własne typy wyjątków dziedzicząc po Exception.

class BledWalidacji(Exception):
    """Własny wyjątek dla błędów walidacji."""
    pass

class UjemnaWartoscError(BledWalidacji):
    """Wyjątek dla ujemnych wartości."""
    pass

def oblicz_pierwiastek(liczba):
    """Oblicza pierwiastek z walidacją."""
    if liczba < 0:
        raise UjemnaWartoscError("Nie można obliczyć pierwiastka z liczby ujemnej!")
    
    return liczba ** 0.5

# Użycie
try:
    wynik = oblicz_pierwiastek(-5)
except UjemnaWartoscError as e:
    print(f"Błąd: {e}")

Assertion – assert

assert służy do sprawdzania założeń w kodzie podczas developmentu.

def podziel(a, b):
    """Dzieli a przez b."""
    assert b != 0, "Dzielnik nie może być zerem!"
    return a / b

# Użycie
wynik = podziel(10, 2)  # OK
wynik = podziel(10, 0)  # AssertionError: Dzielnik nie może być zerem!

Uwaga: assert jest wyłączany w trybie produkcyjnym (python -O). Do walidacji w produkcji używaj if + raise.

Dobre praktyki

  • Łap konkretne błędy
  • Nie ukrywaj błędów
  • Obsługuj tylko to co potrafisz
  • Używaj finally do sprzątania
  • Informacyjne komunikaty błędów

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

Podstawy try-except

  1. Napisz program który próbuje otworzyć plik „dane.txt”. Obsłuż błąd gdy plik nie istnieje.
  2. Pobierz od użytkownika liczbę. Obsłuż błąd gdy wpisze tekst zamiast liczby.
  3. Stwórz listę 3 elementów. Spróbuj pobrać element o indeksie 5 i obsłuż błąd.
  4. Stwórz słownik z 3 kluczami. Spróbuj pobrać nieistniejący klucz i obsłuż błąd.
  5. Napisz program dzielący dwie liczby. Obsłuż dzielenie przez zero.
  6. Spróbuj dodać string do liczby i obsłuż błąd TypeError.
  7. Napisz program który próbuje skonwertować „abc” na int. Obsłuż błąd.
  8. Pobierz od użytkownika indeks listy. Obsłuż błąd gdy podadzielny typ lub poza zakresem.
  9. Otwórz plik do zapisu w katalogu bez uprawnień. Obsłuż PermissionError.
  10. Napisz program który łapie KeyboardInterrupt (Ctrl+C) i wyświetla komunikat.

Wiele except

  1. Napisz kalkulator obsługujący ValueError i ZeroDivisionError.
  2. Napisz funkcję otwierającą plik obsługującą FileNotFoundError i PermissionError.
  3. Pobierz od użytkownika wiek. Obsłuż ValueError i sprawdź czy nie jest ujemny (ValueError).
  4. Napisz program dostępu do słownika obsługujący KeyError i TypeError.
  5. Napisz funkcję konwertującą string na liczbę obsługującą ValueError i TypeError.

Przypisanie błędu do zmiennej

  1. Złap KeyError i wyświetl komunikat zawierający nazwę brakującego klucza.
  2. Złap ValueError i wyświetl dokładny komunikat błędu.
  3. Złap FileNotFoundError i wyświetl nazwę brakującego pliku.
  4. Złap wszystkie wyjątki jako zmienną i wyświetl ich typ.
  5. Złap IndexError i wyświetl komunikat z podanym indeksem.

Blok else

  1. Napisz program który próbuje otworzyć plik. W bloku else wyświetl jego zawartość.
  2. Pobierz liczbę od użytkownika. W else oblicz jej kwadrat.
  3. Otwórz plik do zapisu. W else zapisz dane. Obsłuż błędy.
  4. Pobierz dwie liczby i podziel. W else wyświetl wynik.
  5. Wczytaj dane ze słownika. W else przetwórz dane.

Blok finally

  1. Otwórz plik i zagwarantuj jego zamknięcie w finally.
  2. Napisz funkcję która zawsze wyświetla „Koniec” w finally.
  3. Zaimplementuj licznik prób w finally.
  4. Zapisz log operacji w finally (czy była udana czy nie).
  5. Zmierz czas operacji w finally.

raise – wywoływanie błędów

  1. Napisz funkcję sprawdzającą wiek. Wywołaj ValueError gdy wiek > 150.
  2. Napisz funkcję obliczającą pierwiastek. Wywołaj ValueError dla liczb ujemnych.
  3. Napisz funkcję dzielącą. Wywołaj ZeroDivisionError dla dzielnika = 0.
  4. Napisz funkcję walidującą email. Wywołaj ValueError gdy brak @ .
  5. Napisz funkcję sprawdzającą hasło (min 8 znaków). Wywołaj ValueError gdy za krótkie.
  6. Napisz funkcję która wywołuje TypeError gdy argument nie jest typu int.
  7. Napisz funkcję walidującą PESEL (11 cyfr). Wywołaj ValueError gdy niepoprawny.
  8. Napisz funkcję sprawdzającą czy liczba jest w zakresie 1-100. Wywołaj ValueError gdy nie.
  9. Napisz funkcję która wywołuje FileNotFoundError gdy podano pustą nazwę pliku.
  10. Napisz funkcję walidującą numer telefonu (9 cyfr). Wywołaj ValueError gdy błędny.

Walidacja danych użytkownika

  1. Napisz funkcję pobierającą dodatnią liczbę całkowitą. Powtarzaj dopóki nie będzie poprawna.
  2. Napisz funkcję pobierającą liczbę z zakresu 1-10. Waliduj i powtarzaj.
  3. Napisz funkcję pobierającą tak/nie. Akceptuj tylko te wartości.
  4. Napisz funkcję pobierającą datę w formacie DD-MM-YYYY. Waliduj format.
  5. Napisz funkcję pobierającą ocenę 1-6. Waliduj zakres.

Funkcje z obsługą błędów

  1. Napisz funkcję bezpiecznego dzielenia zwracającą None przy błędzie.
  2. Napisz funkcję bezpiecznego dostępu do listy zwracającą wartość domyślną.
  3. Napisz funkcję bezpiecznego dostępu do słownika z wartością domyślną.
  4. Napisz funkcję bezpiecznie konwertującą na int (zwraca 0 przy błędzie).
  5. Napisz funkcję bezpiecznie otwierającą plik (zwraca None przy błędzie).

Praca z plikami

  1. Napisz program który tworzy plik jeśli nie istnieje, otwiera jeśli istnieje.
  2. Napisz funkcję kopiującą plik z obsługą wszystkich błędów.
  3. Napisz program który próbuje wczytać JSON. Obsłuż błąd parsowania.
  4. Napisz funkcję dopisującą do logu z obsługą błędów.
  5. Napisz funkcję usuwającą plik z obsługą błędów.

Praca ze słownikami i listami

  1. Napisz funkcję bezpiecznie pobierającą wartość zagnieżdżoną ze słownika.
  2. Napisz funkcję aktualizującą słownik z obsługą błędów.
  3. Napisz funkcję usuwającą element z listy po wartości (obsłuż ValueError).
  4. Napisz funkcję sortującą listę mieszanych typów (obsłuż TypeError).
  5. Napisz funkcję łączącą dwa słowniki z obsługą błędów.

Konwersje i parsowanie

  1. Napisz funkcję konwertującą string na float z obsługą błędów.
  2. Napisz funkcję parsującą datę z różnych formatów.
  3. Napisz funkcję konwertującą string na bool (tak/nie, true/false, 1/0).
  4. Napisz funkcję parsującą listę liczb z stringa „1,2,3,4,5”.
  5. Napisz funkcję konwertującą temperaturę z obsługą błędnych jednostek.

Matematyka

  1. Napisz funkcję obliczającą silnię. Wywołaj ValueError dla n < 0.
  2. Napisz funkcję obliczającą pierwiastek z walidacją.
  3. Napisz funkcję obliczającą potęgę. Obsłuż OverflowError.
  4. Napisz funkcję obliczającą logarytm. Waliduj argument > 0.
  5. Napisz funkcję obliczającą procent. Waliduj zakres 0-100.

Kalkulator rozszerzony

  1. Napisz kalkulator z menu i pełną obsługą błędów.
  2. Dodaj historię operacji zapisywaną do pliku (obsłuż błędy).
  3. Dodaj tryb zaawansowany z funkcjami matematycznymi (obsłuż błędy dziedziny).
  4. Dodaj obsługę nawiasów w wyrażeniach.
  5. Dodaj możliwość zapisywania i wczytywania sesji.

Gry i aplikacje

  1. Napisz grę w zgadywanie liczby z pełną walidacją inputu.
  2. Napisz quiz z obsługą błędnych odpowiedzi.
  3. Napisz kółko i krzyżyk z walidacją ruchów.
  4. Napisz prostą grę tekstową z zapisem stanu (obsłuż błędy plików).
  5. Napisz symulator bankomatu z walidacją PIN i kwot.

Własne wyjątki

  1. Stwórz własny wyjątek NiepoprawnyWiekError.
  2. Stwórz własny wyjątek BledWalidacjiEmail.
  3. Stwórz hierarchię wyjątków dla aplikacji bankowej.
  4. Stwórz własny wyjątek BledKonfiguracji z dodatkowymi atrybutami.
  5. Stwórz własny wyjątek PrzekroczonoLimit z informacją o limicie.

Assert

  1. Użyj assert do sprawdzenia czy lista nie jest pusta przed operacją.
  2. Użyj assert do sprawdzenia typu argumentu funkcji.
  3. Użyj assert do sprawdzenia czy liczba jest w zakresie.
  4. Użyj assert do walidacji długości stringa.
  5. Użyj assert do sprawdzenia niezmiennika klasy.

Zadania kompleksowe

  1. Napisz system logowania użytkownika z pełną walidacją (login, hasło, próby).
  2. Napisz program zarządzający kontami bankowymi z walidacją operacji.
  3. Napisz parser plików konfiguracyjnych z obsługą wszystkich błędów.
  4. Napisz system rezerwacji z walidacją dat, dostępności, konfliktów.
  5. Napisz aplikację TODO z zapisem do pliku i pełną obsługą błędów.
  6. Napisz kalkulator kredytu z walidacją wszystkich parametrów.
  7. Napisz system ocen uczniów z walidacją ocen, obliczaniem średnich, zapisem.
  8. Napisz konwerter walut z obsługą błędów API, walidacją kwot.
  9. Napisz aplikację inwentaryzacji z walidacją stanów, cen, operacji.
  10. Napisz prosty interpreter poleceń z obsługą błędów składni i wykonania.

Debugging

  1. Napisz funkcję która loguje wszystkie wyjątki do pliku z datą, typem, traceback.
  2. Napisz dekorator który łapie i loguje wszystkie wyjątki funkcji.
  3. Napisz kontekst menedżer do obsługi zasobów z automatycznym sprzątaniem.
  4. Napisz funkcję która wyświetla czytelny komunikat błędu dla użytkownika końcowego.
  5. Napisz system retry (ponawiania operacji) z wykładniczym cofaniem.

Zaawansowane

  1. Napisz funkcję która łapie wiele typów wyjątków i reaguje inaczej na każdy.
  2. Napisz funkcję używającą re-raise do propagacji błędu z dodatkowym kontekstem.
  3. Napisz chain wyjątków (raise … from …).
  4. Napisz własny context manager używając try-finally.
  5. Napisz wrapper funkcji który zamienia wyjątki na kody błędów.

PODSUMOWANIE

Czym jest wyjątek. Wyjątek to sytuacja, która przerywa normalne wykonanie programu. Jeśli go nie obsłużysz, program kończy działanie z komunikatem błędu.

Struktura bloków:

BlokKiedy się wykonujeObowiązkowy?
tryzawsze pierwszy – tu jest kod, który może wywołać błądtak
excepttylko gdy w try wystąpił błąd (może być kilka)tak (lub finally)
elsetylko gdy w try NIE było błędunie
finallyzawsze, niezależnie od błędunie

Najczęstsze typy błędów:

BłądPrzyczynaPrzykład
FileNotFoundErrorplik nie istniejeopen("brak.txt")
KeyErrorbrak klucza w słownikuslownik["miasto"]
IndexErrorindeks poza zakresem listylista[5] przy 3 elementach
ValueErrortyp się zgadza, wartość nieint("abc")
TypeErroroperacja na niewłaściwym typie"Python" + 5
ZeroDivisionErrordzielenie przez zero10 / 0

Zasady, które trzeba rozumieć:

  • Łap konkretne błędy. Samo except: ukrywa, co się stało, utrudnia debugowanie i może schować poważny błąd.
  • Wiele except pozwala reagować inaczej na różne błędy. Ogólny except Exception as e (jeśli jest) stawiamy na końcu, tak jak w przykładach z lekcji.
  • except TypBledu as e zapisuje obiekt błędu do zmiennej, dzięki czemu możesz wypisać jego komunikat.
  • raise ręcznie wywołuje błąd (np. przy walidacji w funkcji). raise bez argumentów w bloku except rzuca ten sam błąd ponownie (re-raise).
  • Własny wyjątek to klasa dziedzicząca po Exception.
  • assert sprawdza założenia podczas tworzenia programu. Jest wyłączany w trybie python -O, więc do walidacji danych używamy if + raise.

Typowe źródła pomyłek:

  • input() zwraca tekst – int(input(...)) może zgłosić ValueError.
  • try obejmuje tylko te linie, które mogą wywołać błąd – im krótszy blok, tym łatwiej znaleźć przyczynę.
  • Kod w else nie jest chroniony przez except z tego samego try.
  • W przykładzie z lekcji z finally i plik.close(): jeśli samo open() się nie powiedzie, zmienna plik nie powstanie i finally zgłosi kolejny błąd. Dlatego do plików wygodniej i bezpieczniej używać with.
  • str() błędu KeyError zawiera już apostrofy wokół klucza, więc zapis f"'{error}'" da podwójne apostrofy (''nazwisko'').

Minimum, które powinno wejść w pamięć

Jeżeli masz zapamiętać tylko kilka rzeczy z tej lekcji, zapamiętaj te:

  1. Kolejność: try → except → else → finally.
  2. else – tylko gdy nie było błędu. finally – zawsze.
  3. Łap konkretne błędy (except ValueError:), a nie „wszystko” (except:).
  4. Sześć błędów do rozpoznania: FileNotFoundError, KeyError, IndexError, ValueError, TypeError, ZeroDivisionError.
  5. int(input()) → grozi ValueError. Dzielenie → grozi ZeroDivisionError.
  6. except ... as e daje dostęp do komunikatu błędu (print(e)).
  7. raise TypBledu("komunikat") wywołuje błąd, a samo raise w except rzuca go dalej.
  8. Do walidacji danych w programie używaj if + raise, a nie assert.
  9. Wzorzec „pytaj do skutku”: while True + try + return/break po poprawnych danych.

Ściągawka – najważniejsze polecenia i składnia

Pełny szkielet:

try:
    ryzykowna_operacja()
except TypBledu as e:
    print(f"Błąd: {e}")
else:
    print("Bez błędów")
finally:
    print("Zawsze")

Kilka except i jeden dla wielu błędów:

except ValueError:
    ...
except ZeroDivisionError:
    ...
except (ValueError, TypeError) as e:
    ...
except Exception as e:     # ostatni, „awaryjny”
    ...

Wywoływanie błędów:

raise ValueError("Komunikat")   # wywołaj błąd
raise                           # w except: rzuć ten sam błąd dalej
assert b != 0, "Dzielnik nie może być zerem!"   # tylko na czas tworzenia programu

Własny wyjątek:

class BledWalidacji(Exception):
    pass

Wzorzec: pobieranie danych do skutku

while True:
    try:
        liczba = float(input("Podaj liczbę: "))
        break
    except ValueError:
        print("To nie jest liczba!")

Błąd → przyczyna → typowa obsługa:

BłądTypowa obsługa
FileNotFoundErrorkomunikat, utworzenie pliku lub return None
KeyErrorwartość domyślna (alternatywa: slownik.get(klucz, domyslna))
IndexErrorkomunikat z dozwolonym zakresem (0-{len(lista)-1})
ValueErrorponowne pytanie użytkownika
TypeErrorkomunikat lub konwersja (str(5))
ZeroDivisionErrorkomunikat, None lub ponowne pytanie

Quiz – sprawdź się

1. (teoria) Kiedy wykonuje się blok else, a kiedy blok finally w konstrukcji try?

2. Co zostanie wyświetlone dla wartości s = "12" i co dla s = "abc"?

try:
    print("A")
    x = int(s)
    print("B")
except ValueError:
    print("C")
else:
    print("D")
finally:
    print("E")

3. Znajdź błąd. Co się stanie, gdy użytkownik wpisze 0? Jak to naprawić?

try:
    liczba = int(input("Podaj liczbę: "))
    print(10 / liczba)
except ValueError:
    print("To nie jest liczba")

4. Podaj, jaki typ błędu zgłosi każda z poniższych instrukcji:

a) int("x")
b) [1, 2][5]
c) {"a": 1}["b"]
d) "a" + 1
e) 5 / 0
f) open("brak.txt")

5. Uzupełnij kod tak, aby funkcja zgłaszała błąd dla ujemnego wieku:

def ustaw_wiek(wiek):
    if wiek < 0:
        ____ ____("Wiek nie może być ujemny")
    return wiek

6. (składnia) a) Jak zapisać jeden blok except, który obsłuży zarówno ValueError, jak i TypeError i zapisze błąd do zmiennej e? b) Który zapis przypisuje obiekt błędu do zmiennej?

  • except ValueError as e:
  • except ValueError = e:
  • except e(ValueError):

7. Co zostanie wyświetlone?

def dziel(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return None
    finally:
        print("koniec")

print(dziel(6, 3))
print(dziel(1, 0))

8. Co się stanie po uruchomieniu poniższego kodu? Czy zostanie wypisane dalej?

try:
    10 / 0
except ZeroDivisionError:
    print("log")
    raise
print("dalej")

9. (teoria) Czym różni się assert od if + raise i którego użyjesz do sprawdzania danych wpisanych przez użytkownika? Dlaczego?

10. Uzupełnij definicję własnego wyjątku:

class BladHasla(____):
    pass

11. (zadanie praktyczne) Napisz funkcję pobierz_liczbe_calkowita(), która pyta użytkownika o liczbę całkowitą i powtarza pytanie, dopóki dane nie będą poprawne. Poprawną liczbę ma zwrócić.

Odpowiedzi
  1. else wykonuje się tylko wtedy, gdy w try nie wystąpił błąd. finally wykonuje się zawsze, niezależnie od tego, czy błąd wystąpił.
  2. Dla "12": A, B, D, E (każde w osobnej linii). Dla "abc": A, C, E (po błędzie B i D się nie wykonują).
  3. Dla 0 wystąpi ZeroDivisionError, którego żaden except nie obsługuje, więc program zakończy się błędem. Rozwiązanie: dodać
   except ZeroDivisionError:
       print("Nie można dzielić przez zero!")
  1. a) ValueError, b) IndexError, c) KeyError, d) TypeError, e) ZeroDivisionError, f) FileNotFoundError.
  2. raise ValueError
  3. a) except (ValueError, TypeError) as e:, b) except ValueError as e:
  4. Wyświetli się:
   koniec
   2.0
   koniec
   None

finally wykonuje się przy każdym wywołaniu, jeszcze przed zwróceniem wyniku.
8. Wypisze się log, a następnie raise ponownie wywoła ZeroDivisionError. Błąd nie jest już obsłużony, więc program zakończy się błędem, a dalej nie zostanie wypisane.
9. assert służy do sprawdzania założeń podczas tworzenia programu i jest wyłączany w trybie python -O. Dane od użytkownika sprawdzamy przez if + raise, bo ta walidacja musi działać zawsze, także w wersji produkcyjnej.
10. Exception: class BladHasla(Exception):
11. Przykładowe rozwiązanie:

    def pobierz_liczbe_calkowita():
        while True:
            try:
                return int(input("Podaj liczbę całkowitą: "))
            except ValueError:
                print("To nie jest liczba całkowita!")
Kryteria: pętla `while True`, `int(input())` wewnątrz `try`, obsługa `ValueError`, `return` po poprawnych danych.