Skip to content

6. Wyjątki

Zajmiemy się teraz wyjątkami.

  

Program (exceptions_01)

Pierwszy skrypt ilustruje konieczność obsługi wyjątków.


# -*- coding=utf-8 -*-

# wywołuje się błąd
x=4/0

Celowo wywołujemy błąd, aby zobaczyć informacje generowane przez interpreter. Wynik wyświetlany na ekranie jest następujący:

1
2
3
4
Traceback (most recent call last):
  File "D:\data\istia-1112\python\tutoriel\exceptions_01.py", line 4, in <module>
    x=4/0
ZeroDivisionError: integer division or modulo by zero

Wiersz 4 podaje:

  • typ wyjątku: ZeroDivisionError;
  • powiązany komunikat o błędzie: integer division or modulo by zero. Jest on w języku angielskim. Można to zmienić.

Podstawową zasadą programowania jest to, że należy zrobić wszystko, aby uniknąć „niekontrolowanych” awarii, takich jak ta powyżej. Nawet w przypadku błędu program musi zakończyć działanie w sposób poprawny, podając informacje o błędzie, który wystąpił.

Składnia obsługi wyjątków jest następująca:


try:
    actions susceptibles de lancer une exception
except [classe d'exception, ...]:
    actions de gestion de l'exception
finally:
   actions toujours exécutées qu'il y ait exception ou non

W bloku try wykonywanie akcji zostaje przerwane, gdy tylko wystąpi wyjątek. W takim przypadku wykonywanie jest kontynuowane zgodnie z akcjami zawartymi w klauzuli except.

Klauzula


except [MyException, ...]:

przechwytuje wyjątki typu MyException lub pochodne. Gdy w bloku try wystąpi wyjątek, interpreter sprawdza klauzule **except powiązane z tym blokiem w kolejności, w jakiej zostały zapisane. Zatrzymuje się na pierwszej klauzuli except, która pozwala obsłużyć wystąpiony wyjątek. Jeśli nie znajdzie żadnej, wyjątek jest przekazywany do metody wywołującej. Jeśli ta zawiera blok try/except**, wyjątek jest ponownie obsługiwany; w przeciwnym razie jest on przekazywany dalej w górę łańcucha wywoływanych metod. W ostateczności dociera do interpretera języka Python. Interpreter zatrzymuje wówczas wykonywany program i wyświetla komunikat o błędzie podobny do tego pokazanego w poprzednim przykładzie. Zasada jest zatem taka, że program główny musi przechwycić wszystkie wyjątki, które mogą zostać przekazane z wywołanych metod.

Wyjątek zawiera informacje o błędzie, który wystąpił. Można je uzyskać za pomocą następującej składni:


except MyException as informations:

informacje to krotka zawierająca dane związane z wyjątkiem.

Składnia


except MyException, erreur:

przypisuje do zmiennej erreur komunikat o błędzie związany z wyjątkiem.

Aby wygenerować wyjątek, stosuje się składnię


raise MyException(param1, param2, ...)

, gdzie najczęściej MyException jest klasą pochodną klasy Exception. Parametry przekazane do konstruktora klasy będą dostępne w klauzuli „except” struktur przechwytywania wyjątków.

Poniższy skrypt ilustruje te koncepcje.


Program (exceptions_02)

Poniższy skrypt w sposób jawny obsługuje błędy:


# -*- coding=utf-8 -*-

i=0
# wywołuje się błąd i obsługuje się go
x=2
try:
    x=4/0
except ZeroDivisionError, erreur:
    print ("%s : %s ") % (i, erreur)
# wartość x nie uległa zmianie
print "x=%s" % (x)

# próbujemy ponownie
i+=1
try:
    x=4/0
except Exception, erreur:
    # przechwytujemy najbardziej ogólny wyjątek
    print ("%s : %s ") % (i, erreur)

# można przechwycić kilka rodzajów wyjątków 
i+=1
try:
    x=4/0
except ValueError, erreur:
    # ten wyjątek nie występuje w tym miejscu
    print ("%s : %s ") % (i, erreur)
except Exception, erreur:
    # przechwytujemy najbardziej ogólny wyjątek
    print ("%s : (Exception) %s ") % (i, erreur)
except ZeroDivisionError, erreur:
    # przechwytuje się konkretny typ
    print ("%s : (ZeroDivisionError) %s ") % (i, erreur)

# próbujemy ponownie, zmieniając kolejność 
i+=1
try:
    x=4/0
except ValueError, erreur:
    # ten wyjątek nie występuje w tym miejscu
    print ("%s : %s ") % (i, erreur)
except ZeroDivisionError, erreur:
    # przechwytujemy konkretny typ
    print ("%s : (ZeroDivisionError) %s ") % (i, erreur)
except Exception, erreur:
    # przechwytujemy najbardziej ogólny wyjątek
    print ("%s : (Exception) %s ") % (i, erreur)

# klauzula except bez argumentów
i+=1
try:
    x=4/0
except:
    # nie interesuje nas ani rodzaj wyjątku, ani komunikat o błędzie
    print ("%s : il y a eu un probleme ") % (i)

# inny typ wyjątku
i+=1
try:
    x=int("x")
except ValueError, erreur:
    print ("%s : %s ") % (i, erreur)

# wyjątek przenosi informacje w postaci krotki dostępnej dla programu
i+=1
try:
    x=int("x")
except ValueError as infos:
    print ("%s : %s ") % (i, infos)

# można wywoływać wyjątki
i+=1
try:
    raise ValueError("param1","param2","param3")
except ValueError as infos:
    print ("%s : %s ") % (i, infos)

# można tworzyć własne wyjątki
class MyError(Exception):
    pass

# wywołuje się wyjątek MyError
i+=1
try:
    raise MyError("info1","info2", "info3")
except MyError as infos:
    print ("%s : %s ") % (i, infos)

# wywołuje się wyjątek MyError
i+=1
try:
    raise MyError("mon msg d'erreur")
except MyError, erreur:
    print ("%s : %s ") % (i, erreur)

# można wywołać dowolny typ obiektu
class Objet:
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg

i+=1
try:
    raise Objet("pb...")
except Objet as erreur:
    print "%s : %s" % (i, erreur)

# klauzula finally jest zawsze wykonywana
# niezależnie od tego, czy wystąpi wyjątek, czy nie
i+=1
x=None
try:
    x=1
except:
    print "%s : exception" % (i)
finally:
    print "%s : finally x=%s" % (i,x)

i+=1
x=None
try:
    x=2/0
except:
    print "%s : exception" % (i)
finally:
    print "%s : finally x=%s" % (i,x)

Uwagi:

  • wiersze 6–9: obsługa dzielenia przez zero;
  • wiersz 8: przechwytuje się dokładnie ten wyjątek, który wystąpił;
  • wiersz 11: z powodu wystąpienia wyjątku zmienna x nie otrzymała wartości i w związku z tym jej wartość nie uległa zmianie;
  • wiersze 15–19: powtarzamy tę samą procedurę, ale przechwytując wyjątek wyższego poziomu typu Exception. Ponieważ wyjątek ZeroDivisionError pochodzi od klasy Exception, klauzula except go zatrzyma;
  • wiersze 23–33: umieszczamy kilka klauzul except, aby obsłużyć różne typy wyjątków. Wykonana zostanie tylko jedna klauzula except lub żadna, jeśli wyjątek nie spełnia warunków żadnej klauzuli except;
  • wiersze 51–55: klauzula except może nie mieć żadnego argumentu. W takim przypadku zatrzymuje wszystkie wyjątki;
  • wiersze 59–62: wprowadzają wyjątek ValueError;
  • wiersze 66–69: pobierane są informacje przekazywane przez wyjątek;
  • wiersze 73–76: przedstawiają sposób wywołania wyjątku;
  • wiersze 79–84: ilustrują wykorzystanie własnej klasy wyjątku MyError;
  • wiersze 79–80: klasa MyError jest po prostu pochodną klasy bazowej Exception. Nie dodaje ona niczego do swojej klasy bazowej. Teraz jednak można ją wyraźnie wymienić w klauzulach except;
  • wiersze 97–107: pokazują, że w języku Python można w rzeczywistości rzucać dowolnym typem obiektu, a nie tylko obiektami pochodnymi od klasy Exception;
  • wiersze 109–127: ilustrują użycie klauzuli finally.

Wyniki wyświetlane na ekranie są następujące:

0 : integer division or modulo by zero
x=2
1 : integer division or modulo by zero
2 : (Exception) integer division or modulo by zero
3 : (ZeroDivisionError) integer division or modulo by zero
4 : il y a eu un probleme
5 : invalid literal for int() with base 10: 'x'
6 : invalid literal for int() with base 10: 'x'
7 : ('param1', 'param2', 'param3')
8 : ('info1', 'info2', 'info3')
9 : mon msg d'erreur
10 : pb...
11 : finally x=1
12 : exception
12 : finally x=None

Program (exceptions_03)

Ten nowy skrypt ilustruje przekazywanie wyjątków w łańcuchu wywołujących metod:


# -*- coding=utf-8 -*-

# wyjątek własny
class MyError(Exception):
    pass

# trzy metody
def f1(x):
    # nie obsługujemy wyjątków – są one automatycznie przekazywane w górę
    return f2(x)

def f2(y):
    # nie obsługujemy wyjątków – są one automatycznie przekazywane w górę
    return f3(y)

def f3(z):
    if (z % 2) == 0:
        # jeśli z jest parzyste, zgłaszany jest wyjątek 
        raise MyError("exception dans f3")
    else:
        return 2*z

#---------- main

# wyjątki są przekazywane w górę łańcucha wywoływanych metod
# aż do momentu, gdy jakaś metoda je przechwyci. W tym przypadku będzie to metoda main
try:
    print f1(4)
except MyError as infos:
    print "type :%s, arguments : %s" % (type(infos),infos)

# metoda może wzbogacić wyjątki, które przekazuje dalej
def f4(x):
    try:
        return f5(x)
    except MyError as infos:
        # wzbogacamy wyjątek, a następnie ponownie go rzucamy
        raise MyError(infos,"exception dans f4")

def f5(y):
    try:
        return f6(y)
    except MyError as infos:
        # wzbogacamy wyjątek, a następnie ponownie go rzucamy
        raise MyError(infos, "exception dans f5")

def f6(z):
    if (z % 2) == 0:
        # wywołuje się wyjątek
        raise MyError("exception dans f6")
    else:
        return 2*z

#---------- main
try:
    print f4(4)
except MyError as infos:
    print "type :%s, arguments : %s" % (type(infos),infos)

Uwagi:

  • w wierszach 27–30, w wywołaniu main --> f1 --> f2 --> f3 (wiersz 28), wyjątek MyError wygenerowany przez f3 zostanie przekazany aż do main. Zostanie ona wówczas obsłużona przez klauzulę except w wierszu 29;
  • wiersze 55–58: w wywołaniu main --> f4 --> f5 --> f6 (wiersz 56) wyjątek MyError wygenerowany przez f6 zostanie przekazany wstecz aż do main. Zostanie ona następnie przetworzona przez klauzulę except w wierszu 29. Tym razem, podczas przechodzenia w górę łańcucha wywołujących metod, wyjątek MyError jest wzbogacany o informacje dodawane przez każdą metodę, przez którą przechodzi.

Wyniki wyświetlane na ekranie są następujące:

type :<class '__main__.MyError'>, arguments : exception dans f3
type :<class '__main__.MyError'>, arguments : (MyError(MyError('exception dans f6',), 'exception dans f5'), 'exception dans f4')