Menu

Błędy w Pythonie i debugowanie: czytanie tracebacków i naprawa typowych błędów

Przegląd błędów Pythona, które spotkasz najczęściej (KeyError, ValueError, ModuleNotFoundError, EOFError), i nawyków debugowania, które pozwalają szybko je naprawić.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Błędy to sposób, w jaki Python mówi, co się stało

Każdy błąd w Pythonie to obiekt z typem, komunikatem i tracebackiem, czyli łańcuchem wywołań, który do niego doprowadził. Umiejętność dobrego czytania błędów to najważniejsza umiejętność debugowania, jaką możesz zdobyć. Ta strona to przegląd błędów, które naprawdę spotkasz, i nawyków, dzięki którym szybko je naprawisz.

Mechanikę try/except i zgłaszania własnych wyjątków szczegółowo opisuje strona o wyjątkach. Ta strona dotyczy konkretnych błędów i tego, jak je czytać.

Czytanie tracebacku

Uruchom coś, co się psuje:

def divide(a, b):
    return a / b

def report(values):
    for v in values:
        print(divide(10, v))

report([5, 2, 0])

Python wypisuje coś takiego:

Traceback (most recent call last):
  File "script.py", line 8, in <module>
    report([5, 2, 0])
  File "script.py", line 6, in report
    print(divide(10, v))
  File "script.py", line 2, in divide
    return a / b
ZeroDivisionError: division by zero

Czytaj to od dołu:

  1. ZeroDivisionError: division by zero: typ wyjątku i komunikat. To jest to, co poszło źle.
  2. return a / b w divide, linia 2: linia, która faktycznie zgłosiła wyjątek.
  3. print(divide(10, v)) w report, linia 6: wywołanie, które go wywołało.
  4. report([5, 2, 0]) na poziomie modułu, linia 8: miejsce, w którym wszystko się zaczęło.

Poprawka prawie zawsze trafia do dolnej ramki. Gdy biblioteka zgłasza błąd głęboko w swoich wnętrznościach, idź w górę tracebacku do pierwszej ramki w twoim kodzie: to wywołanie, któremu podano złe dane.

Błędy, które spotkasz najczęściej

NameError

"Name 'mesage' is not defined." Powodem jest literówka albo użycie zmiennej, zanim coś do niej przypisano. W nowszych wersjach Python zwykle podpowiada prawdopodobny zamiennik ("Did you mean 'message'?").

Poprawka: sprawdź pisownię i zasięg. Jeśli nazwa istnieje tylko wewnątrz funkcji, nie da się jej odczytać z zewnątrz.

TypeError

"Can only concatenate str (not 'int') to str." Zły typ dla danej operacji. Klasyki: dodawanie napisu do liczby, wywoływanie czegoś, czego nie da się wywołać, przekazanie złej liczby argumentów do funkcji.

Poprawka: konwertuj typy jawnie (str(30), int("30")) albo sprawdź, co naprawdę przekazujesz. F-string zwykle czyta się lepiej niż łączenie różnych typów przez +: f"age: {30}".

ValueError

"Invalid literal for int() with base 10: 'hello'." Typ jest dobry (int() przyjmuje napis), ale wartość nie pasuje. Częste przy int(), float(), parsowaniu dat i funkcjach, które przyjmują argument z ograniczonego zakresu.

Poprawka: sprawdź dane przed konwersją albo złap błąd i go obsłuż:

KeyError

"KeyError: 'charlie'." Klucza nie ma w słowniku. Trzy idiomatyczne poprawki, zależnie od zamiaru:

Jeśli brakujące klucze w słowniku mają się same inicjalizować, warto przyjrzeć się collections.defaultdict.

IndexError

"List index out of range." Prosisz o pozycję, której sekwencja nie ma.

Poprawka: zabezpiecz się sprawdzeniem długości, użyj -1, by odwołać się do ostatniego elementu, albo użyj wycinka (numbers[5:6] zwraca [] zamiast zgłaszać błąd).

AttributeError

"'NoneType' object has no attribute 'upper'." Wywołujesz metodę na czymś, co jej nie ma. Prawie zawsze oznacza to, że zmienna ma nieoczekiwany typ, często None tam, gdzie spodziewasz się prawdziwej wartości.

Poprawka: ustal, skąd wzięło się None. Najszybciej pomoże print(type(var)) albo punkt przerwania tuż przed błędem. Funkcje, które "czasem zawodzą", zwykle zwracają None, więc sprawdź ich wartość zwracaną, zanim wywołasz na niej metody.

ModuleNotFoundError (i ImportError)

import fastapi

"No module named 'fastapi'." Pakiet nie jest zainstalowany w interpreterze Pythona, którego używa twój skrypt. Dwie częste przyczyny:

  1. Pakiet naprawdę nie jest zainstalowany. Uruchom python -m pip install fastapi we właściwym środowisku.
  2. Pakiet jest zainstalowany, ale w innym Pythonie. Na macOS zdarza się to nagminnie, gdy pip i python wskazują różne instalacje.

Niezawodna poprawka to instalowanie tym samym interpreterem, którym uruchamiasz kod:

python -m pip install fastapi

Jeśli używasz środowiska wirtualnego (a warto), upewnij się, że jest aktywne zarówno przed pip install, jak i przed python script.py.

FileNotFoundError

with open("settings.yaml") as f:
    config = f.read()

"[Errno 2] No such file or directory: 'settings.yaml'." Ścieżka nie istnieje względem miejsca, w którym działa skrypt.

Poprawka: wypisz os.getcwd() na początku skryptu, żeby potwierdzić, gdzie Python szuka. Użyj ścieżek bezwzględnych albo pathlib.Path(__file__).parent / "settings.yaml", by zakotwiczyć ścieżki w lokalizacji samego skryptu.

EOFError

name = input("Name: ")

"EOF when reading a line." input() próbuje czytać ze stdin i nic nie dostaje: albo wejście przyszło przez potok z pustego źródła, albo w prompcie naciśnięto Ctrl-D.

Poprawka: jeśli potok jest zamierzony, opakuj wywołanie:

try:
    name = input("Name: ")
except EOFError:
    name = "anonymous"

W edytorze w przeglądarce na tych stronach środowisko symuluje wejście, więc tam tego błędu nie spotkasz.

IndentationError i SyntaxError

IndentationError: expected an indented block after function definition on line 2

Te są wyjątkowe: pojawiają się, zanim program w ogóle ruszy. Python odmawia sparsowania pliku.

  • IndentationError: brakuje ciała def, if, for itp. albo jest źle wyrównane. Większość edytorów pokazuje wcięcia, a włączenie "show whitespace" pozwala wyłapać pomieszane tabulatory i spacje.
  • SyntaxError: brakuje dwukropka, nawiasy się nie zgadzają albo słowo kluczowe ma literówkę. Nowsze wersje Pythona wskazują strzałką (^) winny znak.

Poprawka: komunikat błędu podaje linię. Zajrzyj tam. Jeśli w tej linii nic nie jest oczywiście nie tak, sprawdź linię wcześniejszą: niezamknięty nawias kilka linii wyżej często daje błąd składni dużo dalej.

RuntimeError

Worek na wszystko w stylu "coś poszło źle w czasie działania, ale to nie żaden z bardziej konkretnych błędów". Częstą odmianą jest RecursionError (przekroczona głębokość rekurencji). Kod bibliotek często zgłasza RuntimeError, gdy znajdzie się w złym stanie.

Poprawka: przeczytaj komunikat, zwykle jest opisowy. Jeśli przyczyną jest rekurencja, przerób ją na pętlę iteracyjną albo, w rzadkich przypadkach, podnieś limit przez sys.setrecursionlimit.

Debugowanie przez print

Zanim sięgniesz po prawdziwy debugger, print() (a jeszcze lepiej forma f"{var=}") wyłapie większość błędów:

f"{var=}" wypisuje zarówno kod wyrażenia, jak i jego wartość, więc nie trzeba przepisywać nazwy w napisie formatu. Uruchom skrypt, przejrzyj wyjście i znajdź linię, w której wartość stała się błędna. Większość "tajemniczych" błędów staje się oczywista po trzech, czterech dobrze umieszczonych wywołaniach print.

Usuń te wywołania print przed commitem. W kodzie, który trafi na produkcję, logging.debug(...) jest lepsze niż print, bo logi debugowania da się włączać i wyłączać bez edytowania linii.

breakpoint() i pdb

Gdy print nie wystarcza, breakpoint() przenosi cię w tym miejscu do interaktywnego debuggera Pythona:

def discount(price, percent):
    breakpoint()
    return price * (1 - percent / 100)

discount(100, 20)

Uruchom skrypt w prawdziwym terminalu. Zobaczysz prompt (Pdb). Kilka poleceń, które warto znać:

  • p variable: wypisuje zmienną.
  • n: przechodzi do następnej linii.
  • s: wchodzi do wywoływanej funkcji.
  • c: kontynuuje działanie do następnego punktu przerwania albo do końca.
  • q: kończy.
  • l: pokazuje kod wokół bieżącej linii.

Debuggery w IDE (VS Code, PyCharm) opakowują ten sam protokół w interfejs graficzny: punkty przerwania ustawiane na marginesie, panel boczny ze zmiennymi. Wybierz to, co jest dla ciebie wygodniejsze.

Nawyki, które zmniejszają liczbę błędów

  • Używaj opisowych nazw zmiennych. Połowa zamieszania z TypeError i AttributeError znika, gdy nie da się zapomnieć, co jest w zmiennej.
  • Sprawdzaj dane na wejściu. Parsuj i sprawdzaj dane od użytkownika albo zawartość plików raz, na początku funkcji. Reszta kodu może wtedy ufać wartościom.
  • Zawodź głośno, a nie po cichu. Gołe except Exception: pass ukrywa błędy, które naprawdę musisz zobaczyć. Łap konkretne wyjątki, obsługuj je świadomie, a resztę przepuszczaj dalej.
  • Najpierw czytaj dół tracebacku. Każda minuta poświęcona nauce czytania tracebacków zwraca się stukrotnie.

Większość błędów to nie zagadki, tylko jednolinijkowe literówki albo pomyłki z typami, z jasnym komunikatem. Ufaj komunikatowi błędu: zwykle ma rację.

Udało się

To koniec części referencyjnej. Za tobą droga od "czym jest Python?" przez zmienne, sterowanie przepływem, kolekcje, funkcje, klasy, iterację, prawdziwe dane aż po błędy. Dalsze kroki mają kształt projektów: wybierz coś, co chcesz zbudować, i cofaj się do elementów, które musisz poznać głębiej. Te strony nadal tu będą, gdy wrócisz z konkretnym pytaniem.

Najczęściej zadawane pytania

Czym jest KeyError w Pythonie?

KeyError pojawia się, gdy szukasz w słowniku klucza, którego nie ma. users["missing"] zgłasza KeyError: 'missing'. Bezpieczne alternatywy to users.get("missing", default), users.get("missing") (zwraca None) albo jawne sprawdzenie if key in users:.

Czym jest EOFError w Pythonie?

EOFError (end-of-file, koniec pliku) pojawia się, gdy input() nie może niczego odczytać, bo strumień wejścia został zamknięty. Najczęściej widać go, gdy skrypt używający input() dostaje przez potok puste dane albo gdy w interaktywnym prompcie naciśniesz Ctrl-D. Zabezpiecz się przez try/except EOFError, jeśli skrypt musi obsługiwać wejście z potoku.

Czym jest ModuleNotFoundError?

ModuleNotFoundError znaczy, że import X nie znalazł modułu o nazwie X. Albo pakiet nie jest zainstalowany (pip install X), albo jest zainstalowany w innym Pythonie niż ten, który uruchamia twój kod (bardzo częste, gdy masz kilka instalacji Pythona). python -m pip install X naprawia drugi przypadek, bo używa tego samego interpretera.

Jak czytać traceback w Pythonie?

Od dołu do góry. Ostatnia linia to typ wyjątku i komunikat, czyli konkretna rzecz, która poszła źle. Linie powyżej pokazują stos wywołań, który tam doprowadził, a twój własny kod jest zwykle bliżej dołu. Przejdź do pliku i linii w najniższej ramce, która należy do ciebie: tam trzeba wprowadzić poprawkę.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ