Пользовательские исключения

Создание простейшего пользовательского исключения

Чтобы создать пользовательское исключение, необходимо всего лишь унаследовать от класса Exception. Для большинства целей этого достаточно и тело класса оставляют пустым.

class MyException(Exception):
    pass

raise MyException("Моё первое пользовательское исключение.")
---------------------------------------------------------------------------
MyException                               Traceback (most recent call last)
Input In [1], in <cell line: 4>()
      1 class MyException(Exception):
      2     pass
----> 4 raise MyException("Моё первое пользовательское исключение.")

MyException: Моё первое пользовательское исключение.

В примере создан типа исключений MyException, а сразу после оно возбуждено, что привело к появлению сообщения об ошибке, т.к. оно не обработано. Обрабатывать исключения типа MyException можно, как и любые другие.

try:
    raise MyException("Моё пользовательское исключение!")
except MyException as msg:
    print(msg)
Моё пользовательское исключение!

Т.к. MyException наследует от Exception, то проверка на него тоже будет проходить.

try:
    raise MyException("Моё пользовательское исключение!")
except Exception as msg:
    print(f"Перехвачено какое-то исключение со следующим сообщением:\n{msg}")
Перехвачено какое-то исключение со следующим сообщением:
Моё пользовательское исключение!

Более сложный случай

Исключения являются полноценными классами, что позволяет добавлять им атрибуты, объявлять методы и т.п. Это можно использовать, чтобы вместе с возбуждаемым исключением передавать больше информации, но обычно исключения все же оставляют простыми.

В качестве примера создадим исключение, которое запоминает время, когда оно было возбужденно.

from time import ctime

class MyException(Exception):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.time = ctime()

Теперь в обработчике событий можно получить доступ к атрибуту time.

try:
    raise MyException("Какое-то сообщение!")
except MyException as e:
    print(f'Перехвачено исключение типа MyException со следующим сообщением:\n"{e}"')
    print(f"Время возбуждения исключения: {e.time}.")
Перехвачено исключение типа MyException со следующим сообщением:
"Какое-то сообщение!"
Время возбуждения исключения: Tue Jun  7 15:10:22 2022.

В качестве второго примера приведем класс исключений, который подсчитывает количество раз, в программе возбуждались исключения данного типа.

class CountingException(Exception):
    times_raised = 0 
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        CountingException.times_raised += 1

for i in range(3):
    try:
        raise CountingException("Подсчитывающее исключение возбужденно.")
    except CountingException as msg:
        print(msg)

print(f"Подсчитывающее исключение было возбужденно {CountingException.times_raised} раз!")
Подсчитывающее исключение возбужденно.
Подсчитывающее исключение возбужденно.
Подсчитывающее исключение возбужденно.
Подсчитывающее исключение было возбужденно 3 раз!

Пример

В качестве примера рассмотрим исключение IndexError, которое в частности возбуждается при попытке обращения по индексу за пределы списка. Предположим, что мы хотим знать не только, что мы обратились за пределы массива, но также и в какую конкретно сторону мы промахнулись: ниже минимального допустимого индекса или выше максимального.

Для этого определим два пользовательских типа исключений LowIndexError и HighIndexError.

class LowIndexError(IndexError):
    def __init__(self, index, size):
        message = f"Lowest acceptable index is {-size}, but {index} was given."
        super().__init__(message)

class HighIndexError(IndexError):
    def __init__(self, index, size):
        message = f"Highest acceptable index is {size - 1}, but {index} was given."
        super().__init__(message)

Т.к. обе этих ошибки сигнализируют об ошибке, связанной с индексом, то логично наследовать от IndexError. Конструктор перегружен таким образом, чтобы он принимал индекс и размер коллекции и формировал сообщение с упоминанием значений этих параметров.

Теперь объявим пользовательский класс MyList, который инкапсулирует в себе список своих элементов и делегирует ему все свои методы, но переопределяем доступ к элементам списка (метод __getitem__) таким образом, что он бросает исключение LowIndexError, если индекс ниже минимального, исключение HighIndexError если индекс выше максимального.

Note

Вообще говоря, такая перегрузка метода __getitem__ сломает использование срезов, но в целях простоты оставим срезы за скобками.

class MyList:
    def __init__(self, *elements):
        self._elements = list(elements)

    def __getitem__(self, k):
        n = len(self._elements)
        if k < -n:
            raise LowIndexError(k, n)
        elif k < n:
            return self._elements[k]
        else:
            raise HighIndexError(k, n)
    
    def __getattr__(self, attr):
        return getattr(self._elements, attr)

l = MyList(1, 2, 3)

Теперь обращение к элементу со слишком низким индексом вызовет одну ошибку.

l[-4]
---------------------------------------------------------------------------
LowIndexError                             Traceback (most recent call last)
Input In [9], in <cell line: 1>()
----> 1 l[-4]

Input In [8], in MyList.__getitem__(self, k)
      6 n = len(self._elements)
      7 if k < -n:
----> 8     raise LowIndexError(k, n)
      9 elif k < n:
     10     return self._elements[k]

LowIndexError: Lowest acceptable index is -3, but -4 was given.

А обращение к элементу со слишком высоким индексом — другую.

l[3]
---------------------------------------------------------------------------
HighIndexError                            Traceback (most recent call last)
Input In [10], in <cell line: 1>()
----> 1 l[3]

Input In [8], in MyList.__getitem__(self, k)
     10     return self._elements[k]
     11 else:
---> 12     raise HighIndexError(k, n)

HighIndexError: Highest acceptable index is 2, but 3 was given.

Обе из них можно обработать через IndexError.

try:
    l[-4]
except IndexError as msg:
    print(msg) 
Lowest acceptable index is -3, but -4 was given.