Podstawy wyjątków

https://docs.python.org/3/library/exceptions.html

https://docs.python.org/3/reference/executionmodel.html#exceptions

WPROWADZENIE

Przy próbie uruchomienia kodu Pythona mogą pojawić się co najmniej dwa rodzaje błędów: błędy składniowe (syntax errors) oraz błędy czasu wykonania (runtime errors). Oba rodzaje błędów są obsługiwane przez wyjątki (exceptions). Z drugiej strony, pojawienie się wyjątku nie zawsze oznacza błąd w programie.

BŁĘDY SKŁADNIOWE

Błędy składniowe, inaczej błędy parsowania, wywołane są przez nieprawidłową składnię instrukcji. W efekcie otrzymujemy komunikat z numerem linii z błędem i jej treścią. Mała strzałka pokazuje token przed którym wykryto błąd.


>>> if True print("word")
  File "<stdin>", line 1
    if True print("word")   # brak dwukropka PRZED print
                ^
SyntaxError: invalid syntax

Jeżeli instrukcja jest poprawna składniowo, to w dalszym ciągu mogą się pojawić błędy przy próbie jej uruchomienia (runtime errors). Takie błędy są nazywane wyjątkami i niekoniecznie muszą oznaczać katastrofę.

WYJĄTKI

Wyjątek jest to zdarzenie, które może modyfikować przebieg sterowania w programów. W Pythonie wyjątki wywoływane są automatycznie w momencie wystąpienia błędów i mogą być wywoływane oraz przechwytywane przez nasz kod.

Wyjątki pozwalają nam wyskoczyć z dowolnie dużych części programu do kodu z programu obsługi wyjątku. Jest to spójny sposób reagowania na niezwykłe zdarzenia, narzędzie sterowania wysokiego poziomu.

Wyjątki są przetwarzane przez cztery instrukcje:

Najważniejsze powody wykorzystywania wyjątków:

Kiedy pojawi się błąd w czasie wykonywania programu, tworzony jest wyjątek (exception). Zwykle wtedy program jest zatrzymywany, a Python wypisuje komunikat o błędzie. Tak działa domyślny program obsługi wyjątków. Standardowo komunikat o błędzie obejmuje zgłoszony wyjątek wraz ze śladem stosu, czyli listą wszystkich wierszy oraz funkcji aktywnych w momencie, kiedy nastąpił wyjątek.


# Przykłady poleceń powodujących wyjątki.
# Dzielenie przez zero - ZeroDivisionError.
print(23/0)

# Odwołanie się do nieistniejącego elementu listy - IndexError.
alist = []                        # pusta lista
print(alist[5])

# Odwołanie się do nieistniejącego klucza w słowniku - KeyError.
adict = {}                        # pusty słownik
print(adict["key"])

# Otwarcie do czytania nieistniejącego pliku - IOError.
afile = open("nie_istnieje.txt", "r")

PRZECHWYTYWANIE WYJĄTKÓW

Czasem nie chcemy, aby program zatrzymał się po wystąpieniu wyjątku. Wtedy należy opakować wywołanie w instrukcję try/except/else w celu samodzielnego przechwycenia wyjątku. Jeżeli zależy nam na wykonaniu pewnych działań końcowych, niezależnych od wystąpienia wyjątku, to stosujemy instrukcję try/finally.

Od Pythona 2.5 mamy jedną instrukcję try/except/finally, czyli bloki except i finally mogą wystąpić w jednej instukcji try.


L = []
# L = [1, 2, 3]
try:
    print(L[1])
except IndexError:            # przechwycenie wyjątku
    print("mam wyjątek")   # nasz program obsługi
else:
    print("nie było wyjątku")
print("kontynuuję")

L = []
# L = [1, 2, 3]
try:
    print(L[1])
finally:                      # działania końcowe
    print("zawsze wykonane")
print("kontynuuję")

Jeżeli podczas wykonywania bloku try nie wystąpił wyjątek, to będzie wykonany blok finally, a następnie instrukcje pod instrukcją try. Jeżeli podczas wykonywania bloku try wystąpił wyjątek, to będzie wykonany blok finally, ale potem wyjątek będzie przekazany wyżej.

Po otwarciu pliku do czytania również mogą się pojawić różne błędy, które przerwą program. Można zastosować konstrukcję try/finally, aby na pewno zamknąć otwarty plik.


afile = open("tekstowy.txt")  # najpierw otwieramy plik tekstowy
try:                          # pracujemy na pliku
    text = afile.read()   # czytamy cały tekst
finally:                      # na pewno zamkniemy plik
    afile.close()

Sprawdzenie poprawności nazwy pliku podanej przez użytkownika można wykonać przy pomocy wyjątków.


# Bezpieczna obsługa pliku.
#filename = raw_input("Podaj nazwę pliku: ")   # Py2
filename = input("Podaj nazwę pliku: ")   # Py3
try:                          # pierwszy blok
    afile = open(filename, "r")
except Exception:             # drugi blok (w razie wyjątku)
    print("Nie ma pliku o nazwie {}".format(filename))
else:                         # trzeci blok opcjonalny (nie było wyjątku)
    print("Plik został otwarty")

Można zamknąć taką funkcjonalność wewnątrz funkcji.


# Funkcja zwraca True, jeżeli plik istnieje, lub False, w przeciwnym razie.
def if_exist(filename):
    try:
        afile = open(filename)
        afile.close()
        return True
    except Exception:
        return False

Ogólny format instrukcji try/except/else/finally zawiera wiele opcjonalnych bloków z programami obsługi, choć musi pojawić się przynajmniej jeden.


# Składnia.
try:
    instrukcje            # podstawowe działanie instrukcji
except ExceptionClass1:   # przechwytuje wskazany wyjątek
    instrukcje
except (ExceptionClass2, ExceptionClass3): # przechwytuje wymienione wyjątki
    instrukcje
except ExceptionClass4 as exception1: # przechwytuje wyjątek i jego instancję
    instrukcje
except (ExceptionClass5, ExceptionClass6) as exception2: # przechwytuje wyjątki i instancję
    instrukcje
except:                  # przechwytuje wszystkie (pozostałe) wyjątki
    instrukcje
else:                    # działania przy braku zgłoszenia wyjątku
    instrukcje
finally:                 # działania końcowe
    instrukcje

Należy ostrożnie korzystać z pustej części except, ponieważ może przechwycić nieoczekiwane wyjątki systemowe niezwiązane z naszym kodem albo wyjątki przeznaczone dla innych programów obsługi. Lepsza jest postać except Exception, która ignoruje wyjątki powiązane z systemowymi wyjściami z programu.

Część else jest pomocna przy ustaleniu, czy wyjątek nie został zgłoszony, czy wystąpił i został obsłużony.

ZGŁASZANIE WYJĄTKÓW

Do ręcznego wywoływania wyjątków służy instrukcja raise.


try:
    raise IndexError
except IndexError:            # przechwycenie wyjątku
    print("mam wyjątek")   # nasz program obsługi
print("kontynuuję")

>>> raise IndexError
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError

>>> raise IndexError, "message"   # stara składnia Py2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: message

>>> raise IndexError("message")   # nowa składnia Py2.7, Py3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: message

Do wywołania wyjątku można także wykorzystać wyrażenie assert, które jest wykorzystywana głównie przy debugowaniu kodu. Jest to pomoc dla programistów do wyszukiwania błędów w programie, sprawdza się występowanie "niemożliwych" sytuacji. Nie jest to mechanizm do obsługi błędów czasu wykonania (run-time errors). AssertionError nie powinien się nigdy pojawić, jeżeli program nie ma błędów.


>>> assert False, "message"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: message