Blog JSystems - uwalniamy wiedzę!

Szukaj
Animacja try/except - łapanie błędów
try / except - gdy w bloku try wystąpi błąd, sterowanie przeskakuje do except

Dotychczas w wielu sytuacjach spotykaliśmy się z wyjątkami. Przy próbie dzielenia przez zero, przy próbie dostępu do nieistniejącego elementu słownika czy listy. W przypadku pojawienia się wyjątku program przestaje być wykonywany, dlatego warto reagować na pojawiające się wyjątki.

Obsługa wyjątków

Najpierw spróbujmy wywołać jakiś wyjątek:

print(1/0)
print("dalej")

Gdy spojrzymy na konsolę po próbie uruchomienia powyższego kodu, zobaczymy komunikat o pojawieniu wyjątku:

print(1/0)
ZeroDivisionError: division by zero

Słowo "dalej" nie zostało wypisane na konsoli, czyli program został przerwany w momencie pojawienia się wyjątku. Co jednak gdy chcielibyśmy aby w przypadku pojawienia się wyjątku został on obsłużony przez zaplanowany przez nas sposób, a program kontynuował swoje działanie? Tu z pomocą przychodzi nam konstrukcja try-except-finally-else dostępna w Pythonie.

Skorzystajmy z jej podstawowej formy w celu poprawienia powyższego programu:

try:
    print(1/0)
except:
    print("nie zabanglało")
print("dalej")

Tym razem na konsoli nie pojawił się żaden wyjątek, za to zostało wydrukowane słowo "dalej". Powiedzieliśmy Pythonowi "try" - czyli spróbuj zrobić to, a jak się nie uda "except" to zrób to. Wyjątek pojawiający się w instrukcji "print(1/0)" został przechwycony w bloku except i została wykonana zaplanowana przez nas reakcja sprowadzająca się do wyświetlenia stosownego komunikatu. Ponieważ wyjątek został obsłużony program pracował dalej i na konsoli wyświetliło się również "dalej". Gdyby po instrukcji "print(1/0)" znalazły się jeszcze kolejne, nie zostałyby one wykonane.

Prezentowany kod będzie działał dla każdego wyjątku. Możemy także obsługiwać wskazane rodzaje wyjątków:

try:
    print(1/0)
except ZeroDivisionError:
    print("nie można dzielić przez zero!")

Tym razem obsłużyliśmy jedynie wyjątego dzielenia przez zero (ZeroDivisionError). Gdyby kod spowodował pojawienie się innego wyjątku, nie zostałby on obsłużony. Na taką ewentualność też mamy sposób:

try:
    print(1/0)
except ZeroDivisionError:
    print("nie można dzielić przez zero!")
except:
    print("jakiś inny błąd")

Inny błąd zostanie obsłużony instrukcjami pojawiającymi się po drugim wystąpieniu klauzuli "except". To byłaby taka obsługa ogólna dla wszystkich pozostałych wyjątków, którą musimy umieścić zawsze na końcu. Obługiwać wyjątki możemy też zbiorczo dla kilku wybranych rodzajów wyjątów:

try:
    print(1/0)
except(ZeroDivisionError,IOError):
    print("albo to, albo to")

W powyższym przykładzie dla obu wyjątków obsługa sprowadzałaby się do tych samych czynności.

Możemy też zechcieć odebrać komunikat błędu (np. żeby zarejestrować go w logach), a w takim przypadku nadajemy alias:

try:
    print(1/0)
except ZeroDivisionError as e:
    print(e)

Na konsoli zostaje wyświetlone:

division by zero

Może się też tak zdarzyć że będziemy chcieli wykonać jakąś czynność niezależnie od wystąpienia lub nie wyjątku. Wtedy wykorzystujemy klauzulę finally:

try:
    print(1/0)
except:
    print("wyjątek")
finally:
    print("co by się nie działo to ja się uruchamiam")

Istnieje też możliwość zareagowania w sytuacji gdy wyjątek nie wystąpił:

try:
    print("info")
except ValueError:
    print("wyjątek")
else:
    print("nie bylo wyjatku")

Python umożliwia też wywoływanie wyjątków, nawet jeśli taki wyjątek nie wystąpi "naturalnie":

try:
    raise TypeError()
except TypeError:
    print("to było do przewidzenia...")

Grupy wyjątków - except* (Python 3.11 i nowsze)

Czasem w jednej operacji może naraz wystąpić kilka niezależnych błędów - na przykład gdy przetwarzamy wiele zadań i chcemy zebrać wszystkie problemy, a nie zatrzymać się na pierwszym. Od Pythona 3.11 służą do tego grupy wyjątków (ExceptionGroup) i nowa składnia "except*". Gwiazdka oznacza, że łapiemy wszystkie wyjątki danego typu z całej grupy, nawet jeśli w grupie są też inne rodzaje:

try:
    raise ExceptionGroup('kilka problemów naraz', [
        ValueError('zła wartość'),
        TypeError('zły typ'),
        ValueError('druga zła wartość'),
    ])
except* ValueError as eg:
    print(f'Złapane ValueError: {len(eg.exceptions)}')
except* TypeError as eg:
    print(f'Złapane TypeError: {len(eg.exceptions)}')
Złapane ValueError: 2
Złapane TypeError: 1

Każdy blok "except*" dostaje mniejszą grupę zawierającą tylko pasujące wyjątki. To rozwiązanie przydaje się głównie przy współbieżności (na przykład zadania asyncio uruchamiane równolegle), gdzie kilka zadań może zawieść jednocześnie. W codziennym kodzie nadal najczęściej użyjemy zwykłego "except", ale warto wiedzieć, że grupy istnieją.

Dopiski do wyjątku - metoda add_note (Python 3.11 i nowsze)

Od Pythona 3.11 do przechwyconego wyjątku można dołożyć dodatkowy opis metodą "add_note". Notatka pojawi się w komunikacie błędu (traceback), co ułatwia diagnozę bez zmiany typu wyjątku:

try:
    liczba = int('to nie liczba')
except ValueError as e:
    e.add_note('Wartość pochodziła z pliku konfiguracyjnego config.ini')
    raise

Dzięki temu w tracebacku obok standardowego komunikatu "invalid literal for int()" zobaczymy też naszą podpowiedź, skąd wzięła się błędna dana - a to często najważniejsza informacja przy szukaniu przyczyny.

Komentarze (0)

Musisz być zalogowany by móc dodać komentarz. Zaloguj się przez Google

Brak komentarzy...