Wyjątki jako klasy

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

https://docs.python.org/3/reference/simple_stmts.html#the-assert-statement

WPROWADZENIE

Zalety wyjątków opartych na klasach.


# Nowe podejście - wyjątek to klasa wywiedziona z klasy Exception.
# Do nazwy dajemy suffix "Error", o ile wyjątek to błąd.
# Dla ostrzeżeń w nazwie dodajemy "Warning".
class MyError(Exception):
    """Warto stworzyć wiersz dokumentujący."""
    pass

raise MyError("message")        # wywołanie wyjątku
exception = MyError("a", "b")   # instancja wyjątku
# W atrybucie args znajduje się krotka argumentów konstruktora domyślnego.
print(exception.args)   # ('a', 'b')
# Domyślnie str() wyświetla zawartość atrybutu args.
print(exception)   # ('a', 'b')

try:
    raise MyError("message")   # instancja
#except MyError:
except MyError as exception:
    print("przechwycenie MyError")
    print(exception.args)

Jeżeli wyjątek został wyzwolony z argumentami, to w instancji wyjątku są domyślnie przechowywane jako krotka w atrybucie args. Dla wygody klasa Exception definiuje metodę __str__() wyświetlającą argumenty, dzięki czemu nie musimy bezpośrednio odwoływać się do atrybutu args.

Można zdefiniować własny konstruktor wyjątku (__init__). Podobnie można określić własny sposób wyświetlania wyjątku (__str__).


class MyError(Exception):

    def __init__(self, value):   # nasz konstruktor wyjątku
        self.value = value

    def __str__(self):   # zmiana sposobu wyświetlania wyjątku
        return repr(self.value)

try:
    raise MyError(2*2)    # instancja wyjątku
except MyError as exception:  # Py2.6+, Py3
# except MyError, exception:   # Py2.6-
# Python 3 interpretuje przecinek jako oznaczający krotkę.
# Obiekt exception jest to zgłoszona instancja klasy MyError.
    print("mam wyjątek, value {}".format(exception.value))
    print("mam wyjątek, value {}".format(exception))   # jw, bo jest __str__

import sys

print("Zgłoszono wyjątek {}".format(sys.exc_info()))

Ostatnio zgłoszony i przechwycony wyjątek jest dostępny ogólnie, jako drugi argument krotki wyników wywołania sys.exc_info(), (typ, wartość, ślad), (type, value, traceback). Jeżeli żaden wyjątek nie jest obsługiwany, to zwracana krotka ma postać (None, None, None).

INSTRUKCJA RAISE

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


# Składnia.
# Zgłoszenie instancji klasy [najbardziej typowe].
# raise instancja_wyjątku
raise IndexError()
raise IndexError("message")

# Zgłoszenie obiektu klasy - instancja klasy zostanie utworzona automatycznie.
# raise klasa_wyjątku
raise IndexError

# Ponowne zgłoszenie ostatniego wyjątku.
raise

# Zgłoszenie nowego wyjątku z wykorzystaniem kontekstu starego wyjątku.
raise new_exception from old_exception   # Py3
raise new_exception from None

# Dla wbudowanych wyjątków mamy równoważne formy.
raise KeyError
raise KeyError()

# Utworzenie instancji wyjątku z wyprzedzeniem.
exception = IndexError()
raise exception

INSTRUKCJA ASSERT

Instrukcja assert jest składniowym skrótem dla często wykorzystywanego w debugowaniu wzorca z instrukcją raise. Najczęściej wykorzystuje się ją do weryfikowania warunków programu w czasie jego tworzenia (tzw. sytuacje niemożliwe). Pomaga wcześnie wykrywać pewne błędy w programie. Ponadto assert działa jako dokumentacja dla innych deweloperów czytających kod, pozwala lepiej zrozumieć stan systemu w danym punkcie programu.

Nie powinno się używać assert do obsługi błędów użytkownika, czy innych błędów pojawiających się w trakcie pracy programu.

Instrukcję assert można usunąć z kodu bajtowego skompilowanego programu, jeżeli w wierszu poleceń Pythona użyjemy opcji -O (python -O main.py), tym samym optymalizując program.


# Składnia podstawowa.
assert expression

# Równoważny kod.
if __debug__:
    if not expression:
        raise AssertionError()

# Składnia rozszerzona.
assert expression1, expression2

# Równoważny kod.
if __debug__:
    if not expression1:
        raise AssertionError(expression2)
# expression2 to zwykle komunikat (string)

>>> x = 1
>>> assert x == 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
>>> assert x == 0, "x have to be zero"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: x have to be zero
>>>

HIERARCHIA KLAS DLA WYJĄTKÓW WBUDOWANYCH

Kiedy except z instrukcji try wymienia klasę nadrzędną, przechwytuje instancje tej klasy, a także instancje wszystkich jej klas podrzędnych.

Warto korzystać z wyjątków wbudowanych i rzucać je w sytuacjach podobnych do tych, w jakich robi to interpreter. Nie ma jednak mechanizmów zabezpieczających przed rzuceniem nieodpowiedniego wyjątku przez kod użytkownika. Można dziedziczyć z wyjątków wbudowanych, ale należy unikać dziedziczenia wielobazowego ze względu na specyficzne zarządzanie pamięcią w wyjątkach i możliwe konflikty.

W Pythonie 3.11 pojawiły się 'grupy wyjątków' (exception groups), które są używane kiedy trzeba jednocześnie rzucić wiele niepowiązanych ze sobą wyjątków. Do stworzenia grupy wyjatków wykorzystuje się dziedziczenie z klasy 'ExceptionGroup'.


>>> import exceptions   # Py2.6+
>>> help(exceptions)   # drzewo klas

>>> import builtins   # Py3, built-in functions, exceptions, and other objects
>>> help(builtins)

BaseException
    +-- BaseExceptionGroup (Py3.11)
    +-- SystemExit   # rzucany przez sys.exit()
    +-- KeyboardInterrupt   # rzucany po naciśnięciu Ctrl+C lub Delete
    +-- GeneratorExit   # rzucany przez generator.close() i coroutine.close()
    +-- Exception   # klasa bazowa dla wyjątków wbudowanych i wyjątków użytkownika
        +-- StopIteration
        +-- StandardError
        |    +-- ArithmeticError
        |    +-- AssertionError
        |    +-- AttributeError
        |    +-- NameError
        |    +-- SyntaxError
        |        +-- IndentationError
        |    +-- TypeError
        |    +-- ValueError
        |    +-- ImportError
        |    +-- MemoryError
        |    +-- RuntimeError
        |        +-- NotImplementedError
        |    +-- EnvironmentError
        |        +-- IOError
        |        +-- OSError
        |    +-- LookupError
        |        +-- IndexError
        |        +-- KeyError
        |    +-- ExceptionGroup [BaseExceptionGroup] (Py3.11)
        |    +-- ...
        +-- Warning
            +-- DeprecationWarning [PEP 387]
            +-- UserWarning
            +-- SyntaxWarning
            +-- RuntimeWarning
            +-- ...

Wybrane wyjątki.