Błędy to po prostu wartości ze złym charakterem
Gdy w Pythonie coś idzie nie tak (dzielenie przez zero, odczyt brakującego pliku, parsowanie złej liczby), środowisko tworzy obiekt wyjątku i zaczyna zwijać stos wywołań, aż coś go złapie. Jeśli nic go nie złapie, program kończy działanie i wypisuje traceback.
Wyjątki same w sobie nie są złe. To sposób, w jaki Python sygnalizuje "nie mogę kontynuować tej operacji, a oto dlaczego". Twoim zadaniem jest decydować w każdym przypadku osobno, po których umiesz się pozbierać, a które należy przepuścić wyżej.
Podstawowy kształt
try:otwiera blok ryzykownego kodu.except ValueError:łapie ten konkretny wyjątek, jeśli zostanie zgłoszony wtry.- Jeśli żaden wyjątek nie wystąpi,
exceptjest całkowicie pomijany.
Uruchom fragment z 42: działa. Uruchom go z hello: wykonuje się obsługa błędu.
Łapanie konkretnych wyjątków
Python ma hierarchię typów wyjątków. Kilka, które spotkasz często:
ValueError: wartość jest w jakiś sposób niepoprawna (int("abc"), argumenty spoza zakresu).TypeError: użyto złego typu ("hi" + 3).KeyError: nie znaleziono klucza w słowniku.IndexError: indeks sekwencji jest poza zakresem.FileNotFoundError: plik nie istnieje.ZeroDivisionError: próba dzielenia przez zero.AttributeError: obiekt nie ma żądanego atrybutu.
Łap ten konkretny, który umiesz obsłużyć:
Kilka wyjątków możesz złapać w jednej klauzuli, przekazując krotkę:
Zwróć uwagę na as e. To wiąże obiekt wyjątku z e, żeby można było sprawdzić jego komunikat albo atrybuty.
Nie łap wszystkiego
Gołe except: łapie dosłownie wszystko, łącznie z KeyboardInterrupt (twoim Ctrl-C) i wyjściem na poziomie systemu. Nie używaj go.
except Exception: jest trochę lepsze, ale nadal niebezpieczne: połyka nieprzewidziane błędy i ukrywa prawdziwe źródło problemów:
# Nie rób tego bez naprawdę dobrego powodu.
try:
do_something()
except Exception:
pass
Właściwy ruch to prawie zawsze łapanie konkretnego wyjątku, po którym umiesz się pozbierać. Jeśli nieoczekiwany wyjątek dotrze na szczyt programu, traceback powie ci dokładnie, co poszło nie tak, i to jest zaleta, a nie wada.
else i finally
Instrukcja try ma jeszcze dwie opcjonalne klauzule:
elsewykonuje się, jeśli bloktryzakończył się bez wyjątku.finallywykonuje się zawsze, niezależnie od tego, czy był wyjątek.
else to najczystsze miejsce na kod "tylko przy sukcesie": blok try nie powinien robić więcej niż ta część, która naprawdę może zawieść. finally służy do sprzątania, które musi się wykonać nawet przy niepowodzeniu: zamknięcia zasobu, zwolnienia blokady, przywrócenia stanu.
Samodzielne zgłaszanie wyjątków
Użyj raise, by zasygnalizować błąd we własnym kodzie:
Wybierz typ wyjątku pasujący do tego, co poszło źle. Najpierw sięgaj po wbudowane (ValueError, TypeError, FileNotFoundError), zanim zdefiniujesz własną klasę.
Definiowanie własnego wyjątku
Gdy wbudowane wyjątki nie oddają znaczenia, zdefiniuj własny:
Dziedzicz po Exception (albo po bardziej konkretnym wbudowanym wyjątku) i dodaj klasie docstring. Zwykle tyle wystarczy. Własne wyjątki pozwalają wywołującym łapać tylko ten błąd, który ma znaczenie w ich dziedzinie.
raise ... from ...: łańcuchy wyjątków
Gdy jeden wyjątek wywołuje drugi, zachowaj łańcuch:
from e dołącza oryginalny błąd. Gdy traceback jest wypisywany, Python pokazuje oba: ConfigError, który wypłynął na wierzch, i FileNotFoundError, który go spowodował. Taki ślad jest bezcenny przy debugowaniu.
Menedżery kontekstu: czystszy sposób na sprzątanie
finally jest w porządku, ale dla zasobów takich jak pliki menedżer kontekstu (to, czego używa with) jest prawie zawsze lepszy:
# wersja z finally
f = open("data.txt")
try:
data = f.read()
finally:
f.close()
# wersja z with
with open("data.txt") as f:
data = f.read()
Obie są bezpieczne. Forma z with jest krótsza i działa automatycznie. Po finally sięgaj tylko wtedy, gdy robisz coś, czego biblioteka standardowa nie opakowuje już w menedżer kontekstu.
Kiedy nie łapać
Złapanie wyjątku to decyzja: mówisz "umiem to obsłużyć". Jeśli nie umiesz, pozwól wyjątkowi się propagować. Taki kod to prawie zawsze błąd:
try:
do_work()
except Exception:
pass # po cichu ignoruje wszystko
Uciszanie błędów sprawia, że stają się niewidoczne. Lepiej wyłożyć się głośno, niż kuśtykać dalej w niespójnym stanie.
Podsumowanie
try/exceptpozwala obsłużyć błędy, po których da się pozbierać.- Łap konkretne wyjątki, a nie
Exceptionna ślepo. raisesygnalizuje błędy we własnym kodzie.- Bloki
withzastępują większość sprzątania wfinally. - W razie wątpliwości pozwól wyjątkowi się propagować.
Dalej: przegląd konkretnych błędów, które Python zgłasza najczęściej (KeyError, ValueError, ModuleNotFoundError i kilka innych), oraz nawyków debugowania, które pozwalają szybko je naprawić.
Najczęściej zadawane pytania
Jak obsługiwać błędy w Pythonie?
Umieść ryzykowny kod w bloku try, a konkretny wyjątek złap w bloku except: try: risky() except ValueError: .... Opcjonalne else wykonuje się, gdy nie wystąpił żaden wyjątek, a finally wykonuje się zawsze i służy do sprzątania.
Czy dla bezpieczeństwa łapać Exception?
Nie. Gołe except: albo except Exception: ukrywa błędy, których nie przewidziano. Łap konkretny wyjątek, po którym umiesz się pozbierać, a całą resztę przepuszczaj dalej, żeby widzieć prawdziwy problem.
Czym różni się raise od raise from?
raise NewError(...) zgłasza nowy wyjątek. raise NewError(...) from original zachowuje oryginalny wyjątek jako przyczynę, którą Python pokazuje w tracebacku. Używaj from, gdy błąd niskiego poziomu wywołał błąd wyższego poziomu, który chcesz pokazać.