Typowe wyjątki, czytanie traceback, techniki znajdowania i naprawy błędów.
Gdy Python napotka błąd — wypisuje traceback (stos wywołań). Czytaj go od dołu — ostatnia linia to typ błędu i jego opis, linie wyżej to ścieżka do miejsca błędu.
NazwaWyjatku: opis — co poszło nie takFile "...", line X, in ... — gdzie dokładnie# Kod z błędem:
def oblicz_srednia(oceny):
return sum(oceny) / len(oceny)
klasa = {"Kasia": [5,4,5], "Marek": []}
for imie, oceny in klasa.items():
print(oblicz_srednia(oceny))
# Traceback (most recent call last):
# File "program.py", line 6, in <module>
# print(oblicz_srednia(oceny))
# File "program.py", line 2, in oblicz_srednia
# return sum(oceny) / len(oceny)
# ZeroDivisionError: division by zero
#
# Czytamy od dołu:
# → ZeroDivisionError: dzielenie przez zero
# → w funkcji oblicz_srednia, linia 2
# → wywołana z linii 6 w głównym kodzie
# → Marek ma pustą listę ocen!
#
# Naprawa:
def oblicz_srednia(oceny):
if not oceny:
return 0
return sum(oceny) / len(oceny)
| Wyjątek | Typowa przyczyna | Naprawa |
|---|---|---|
SyntaxError |
błąd składni — brak dwukropka, nawiasu, błędne wcięcie | sprawdź linię wskazaną przez Python — i linię wyżej |
IndentationError |
mieszanie spacji i tabów, złe wcięcie bloku | używaj wyłącznie spacji (4) lub wyłącznie tabów |
NameError |
użycie niezdefiniowanej zmiennej lub literówka | sprawdź nazwę zmiennej, czy jest zdefiniowana przed użyciem |
TypeError |
operacja na złym typie: "5" + 3, wywołanie nie-funkcji |
sprawdź typy zmiennych przez type(), dodaj konwersję |
ValueError |
int("abc"), math.sqrt(-1) |
waliduj dane przed konwersją, użyj try/except |
IndexError |
indeks poza zakresem listy | sprawdź czy indeks < len(lista), użyj if lista |
KeyError |
brak klucza w słowniku | użyj dict.get() lub if key in dict |
ZeroDivisionError |
dzielenie przez 0 | sprawdź mianownik przed dzieleniem: if b != 0 |
AttributeError |
metoda nie istnieje dla danego typu, zmienna to None | sprawdź typ zmiennej, czy nie jest None przed wywołaniem |
RecursionError |
brak warunku bazowego lub nieskończona rekurencja | dodaj/sprawdź warunek bazowy, czy problem maleje |
FileNotFoundError |
błędna ścieżka do pliku, plik nie istnieje | sprawdź ścieżkę, użyj os.path.exists() |
UnicodeDecodeError |
złe kodowanie pliku | dodaj encoding="utf-8" do open() |
# TypeError — dodawanie różnych typów
wiek = input("Wiek: ")
# print(wiek + 1) ← TypeError: str + int
print(int(wiek) + 1) # naprawa: konwersja
# IndexError — poza zakresem
lista = [1, 2, 3]
# print(lista[5]) ← IndexError
if len(lista) > 5:
print(lista[5])
# KeyError — brak klucza
d = {"a": 1, "b": 2}
# print(d["c"]) ← KeyError
print(d.get("c", "brak")) # naprawa: get()
# AttributeError — None zamiast obiektu
def znajdz(lista, x):
for elem in lista:
if elem == x:
return elem
# Brak return → zwraca None
wynik = znajdz([1,2,3], 9)
# print(wynik.upper()) ← AttributeError: None
if wynik is not None:
print(wynik)
# ZeroDivisionError
a, b = 10, 0
# print(a / b) ← ZeroDivisionError
print(a / b if b != 0 else "dzielenie przez 0")
Blok try/except pozwala elegancko obsłużyć błąd zamiast crashować program. Używaj go gdy błąd jest możliwy ale nie pewny.
try — kod który może rzucić wyjątekexcept NazwaWyjatku — obsłuż konkretny wyjątekexcept Exception as e — złap każdy wyjątek i dostęp do jego opisuelse — wykonaj gdy try zakończyło się BEZ wyjątkufinally — wykonaj zawsze — z wyjątkiem i bezexcept (TypeError, ValueError)except: lub except Exception ukrywa błędy których nie przewidziałeś. Łap konkretne wyjątki — tylko te które spodziewasz się obsłużyć.
def bezpieczne_dzielenie(a, b):
try:
wynik = a / b
except ZeroDivisionError:
print("Błąd: dzielenie przez zero!")
return None
except TypeError as e:
print(f"Błąd typu: {e}")
return None
else:
# Wykonuje się gdy NIE było wyjątku
print(f"Sukces: {a} / {b} = {wynik:.4f}")
return wynik
finally:
# Wykonuje się ZAWSZE
print("Operacja zakończona.")
bezpieczne_dzielenie(10, 3)
bezpieczne_dzielenie(10, 0)
bezpieczne_dzielenie(10, "a")
# Wielokrotne wczytywanie aż do poprawnej wartości
while True:
try:
n = int(input("Podaj liczbę całkowitą: "))
break # sukces — wyjdź z pętli
except ValueError:
print("To nie jest liczba całkowita!")
Debugowanie to systematyczne szukanie i naprawianie błędów. Masz kilka narzędzi — od prostych printów do wbudowanego debuggera.
print() w kluczowych miejscach# 1. Print debugging
def sortuj(lista):
print(f"DEBUG wejście: {lista}")
n = len(lista)
for i in range(n-1):
for j in range(n-1-i):
print(f"DEBUG porównuję [{j}]={lista[j]}"
f" z [{j+1}]={lista[j+1]}")
if lista[j] > lista[j+1]:
lista[j], lista[j+1] = \
lista[j+1], lista[j]
print(f"DEBUG wyjście: {lista}")
return lista
# 2. type() do sprawdzenia typów
x = input("Podaj liczbę: ")
print(f"Typ przed konwersją: {type(x)}")
x = int(x)
print(f"Typ po konwersji: {type(x)}")
# 3. assert — sprawdź założenia
def pierwiastek(n):
assert n >= 0, f"n musi być >= 0, got {n}"
return n ** 0.5
# 4. Debugger pdb
import pdb
def buggy():
x = 10
pdb.set_trace() # zatrzymaj tu
y = x * 2
return y
# W debuggerze: n=następna linia, p x=print x, c=continue
Błędy logiczne nie powodują wyjątków — program się uruchamia, ale wynik jest nieprawidłowy. To najtrudniejszy rodzaj błędów.
range(n) vs range(n+1))x = x + 1 przed czy po warunku?b = a to nie kopia! Modyfikujesz oryginał.# BŁĄD 1: Off-by-one
# Chcemy: suma od 1 do 10
suma = 0
for i in range(10): # ← 0..9, pomija 10!
suma += i
print(suma) # 45, nie 55
# NAPRAWA:
suma = sum(range(1, 11)) # 55 ✓
# BŁĄD 2: Alias listy
a = [1, 2, 3]
b = a # b to ALIAS, nie kopia!
b.append(99)
print(a) # [1, 2, 3, 99] — zmienione!
# NAPRAWA:
b = a.copy() # lub a[:]
# BŁĄD 3: Zła inicjalizacja minimum
def znajdz_min(lista):
minimum = 0 # ← błąd! co jeśli wszystkie > 0?
for x in lista:
if x < minimum:
minimum = x
return minimum
# Dla [3,5,2] zwróci 0, nie 2!
# NAPRAWA:
def znajdz_min(lista):
minimum = lista[0] # inicjalizuj pierwszym elementem
for x in lista[1:]:
if x < minimum:
minimum = x
return minimum
# BŁĄD 4: Float — błąd zaokrąglenia
print(0.1 + 0.2 == 0.3) # False !
print(0.1 + 0.2) # 0.30000000000000004
# NAPRAWA:
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True
Pisanie kodu odpornego na błędy to umiejętność — nie tylko naprawiać błędy, ale pisać tak żeby ich unikać.
if lista: przed operacjamid[k]# Walidacja wejścia — zawsze
def oblicz_srednia(oceny):
if not oceny: # pusta lista
return None
if not all(isinstance(o, (int, float))
for o in oceny):
raise TypeError("Oceny muszą być liczbami")
return sum(oceny) / len(oceny)
# get() zamiast [] dla słowników
config = {"host": "localhost"}
port = config.get("port", 5000) # domyślnie 5000
# Sprawdzanie zakresu indeksu
def pobierz(lista, i):
if 0 <= i < len(lista):
return lista[i]
return None # zamiast IndexError
# Testowanie funkcji
def test_srednia():
assert oblicz_srednia([4,5,3]) == 4.0
assert oblicz_srednia([10]) == 10.0
assert oblicz_srednia([]) is None
print("Testy OK!")
test_srednia()
# Czytelne nazwy zmiennych
# Źle:
x = [i for i in range(100) if i % 2 == 0]
# Dobrze:
parzyste_do_100 = [n for n in range(100)
if n % 2 == 0]
Poniższy kod ma 3 błędy logiczne. Znajdź je, wyjaśnij co jest nie tak i napisz poprawną wersję.
def bubble_sort_buggy(lista):
n = len(lista)
for i in range(n): # błąd 1
for j in range(n - i): # błąd 2
if lista[j] > lista[j+1]:
temp = lista[j]
lista[j] = lista[j+1]
lista[j+1] = lista[j] # błąd 3
return lista
dane = [5, 3, 8, 1, 9, 2]
print(bubble_sort_buggy(dane))
range(n) robi 6, co jest zbędne i może prowadzić do błędu w wewnętrznej pętli.
lista[j+1]. Gdy j=n-i-1, j+1=n-i — to wyjście poza zakres. Musi być range(n-1-i) żeby j+1 ≤ n-1.
lista[j] = lista[j+1] oryginalna wartość lista[j] jest nadpisana. Trzecia linia powinna być lista[j+1] = temp.
Poniższy kod ma błędy które spowodują wyjątki lub złe wyniki. Zidentyfikuj każdy błąd, wyjaśnij jaki wyjątek spowoduje i napisz poprawioną wersję.
def analiza_pliku(nazwa):
wyniki = {}
f = open(nazwa) # błąd 1
for linia in f:
czesci = linia.split(",")
imie = czesci[0]
wynik = int(czesci[1]) # błąd 2
wyniki[imie] += wynik # błąd 3
f.close()
najlepszy = max(wyniki) # błąd 4
return najlepszy
print(analiza_pliku("dane.csv"))
open(nazwa) bez encoding="utf-8" może dać UnicodeDecodeError dla polskich znaków. Brak with — plik może nie zostać zamknięty przy wyjątku. Naprawa: with open(nazwa, "r", encoding="utf-8") as f.
czesci[1] może zawierać \n na końcu ostatniej linii lub spacje. int(" 95\n") rzuci ValueError. Naprawa: int(czesci[1].strip()).
wyniki[imie] += wynik — gdy imię pojawia się po raz pierwszy, klucz nie istnieje → KeyError. Naprawa: wyniki[imie] = wyniki.get(imie, 0) + wynik.
max(wyniki) zwraca największy klucz (imię alfabetycznie), nie wartość. Naprawa: max(wyniki, key=wyniki.get) zwraca klucz z największą wartością.
Debugowanie to umiejętność — ćwicz aktywnie szukając błędów zamiast tylko ich unikać.
Bezpieczny kalkulator
Napisz kalkulator który obsługuje wszystkie możliwe błędy: dzielenie przez 0, zły typ wejścia, nieznane działanie. Użyj try/except dla każdego przypadku osobno.
Znajdź błąd — Fibonacci
Ta funkcja fib(n) ma błąd — zwraca złe wyniki dla niektórych n. Znajdź go używając printów. Wskazówka: sprawdź fib(0), fib(1), fib(2).
def fib(n): return n if n < 2 else fib(n-1) + fib(n-3)Testy jednostkowe
Napisz funkcję testuj(func, wejscie, oczekiwane) która sprawdza wynik funkcji i wypisuje PASS lub FAIL z detalami. Przetestuj nią wszystkie algorytmy z kafelka 10.
wynik = func(wejscie), porównaj z oczekiwane, wypisz obie wartości przy FAILLogowanie błędów do pliku
Napisz dekorator @loguj_bledy który łapie wyjątki funkcji i zapisuje je do pliku errors.log z datą, nazwą funkcji i treścią błędu — zamiast crashować program.
def dekorator(func): def wrapper(*args): try: ... except Exception as e: ...Przypadki brzegowe
Dla każdej z funkcji: znajdz_min(lista), srednia(lista), silnia(n) — napisz listę przypadków brzegowych i przetestuj czy funkcja je obsługuje poprawnie.
Naprawa uszkodzonego CSV
Masz plik CSV z "brudnymi" danymi — brakujące wartości, złe typy, duplikaty, puste linie. Napisz program który wczytuje plik, naprawia/odrzuca błędne wiersze i zapisuje oczyszczone dane do nowego pliku z raportem ile wierszy naprawiono.