Сообщить об ошибке.

Исключения, определенные пользователем в языке Python

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

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

В примере создается определяемое пользователем исключение CustomError(), которое наследуется от класса Exception. Это новое исключение, как и другие исключения, может быть вызвано с помощью оператора raise с дополнительным сообщением об ошибке.

# Определяем собственное исключение
>>> class CustomError(Exception):
...     pass
...
>>> raise CustomError
# Traceback (most recent call last):
# ...
# __main__.CustomError

# Вызываем собственное исключение 
# 'CustomError' с сообщением об ошибке
>>> raise CustomError("An error occurred")
# Traceback (most recent call last):
# ...
# __main__.CustomError: An error occurred

При разработке программы на Python, хорошей практикой считается помещать все определяемые пользователем исключения в отдельный файл. Многие стандартные модули определяют свои исключения отдельно как exceptions.py или errors.py (обычно, но не всегда).

Пользовательский класс исключений может реализовать все, что может делать обычный класс, но обычно их делают простыми и краткими. Большинство реализаций пользовательских исключений объявляют настраиваемый базовый класс и наследуют другие классы исключений из этого базового класса.

Большинство пользовательских исключений определяются именами, которые заканчиваются на "Error", аналогично именованию стандартных исключений.

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

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

# определение пользовательских исключений
class Error(Exception):
    """Базовый класс для других исключений"""
    pass

class ValueTooSmallError(Error):
    """Вызывается, когда входное значение мало"""
    pass

class ValueTooLargeError(Error):
    """Вызывается, когда входное значение велико"""
    pass

# число, которое нужно угадать
number = 10

# игра продолжается до тех пор, 
# пока пользователь его не угадает
while True:
    try:
        i_num = int(input("Ввести число: "))
        if i_num < number:
            raise ValueTooSmallError
        elif i_num > number:
            raise ValueTooLargeError
        break
    except ValueTooSmallError:
        print("Это число меньше загаданного, попробуйте еще раз!\n")
    except ValueTooLargeError:
        print("Это число больше загаданного, попробуйте еще раз!\n")

print("Поздравляю! Вы правильно угадали.")

В примере определен базовый класс под названием Error(). Два других исключения, которые фактически вызываются программой (ValueTooSmallError и ValueTooLargeError), являются производными от класса Error().

Это стандартный способ определения пользовательских исключений в программировании на Python, но ни кто не ограничен только этим способом.

Пример запуска скрипта с примером:

Ввести число: 12
Это число больше загаданного, попробуйте еще раз!

Ввести число: 0
Это число меньше загаданного, попробуйте еще раз!

Ввести число: 8
Это число меньше загаданного, попробуйте еще раз!

Ввести число: 10
Поздравляю! Вы правильно угадали.

Смотрим еще один простенький пример.

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

class Error(Exception):
    """Базовый класс для исключений в этом модуле."""
    pass

class InputError(Error):
    """Исключение для ошибок во входных данных.
    Attributes:
        expression -- выражение, в котором произошла ошибка
        message -- объяснение ошибки
    """
    def __init__(self, expression, message):
        self.expression = expression
        self.message = message


x = input("Ведите положительное целое число: ")
try:
    x = int(x)
    if x < 0:
        raise InputError(f'!!! x = input({x})', 
                         '-> Допустимы только положительные числа.')
except ValueError:
    print("Error type of value!")
except InputError as e:
    print(e.args[0])
    print(e.args[1])
else:
    print(x)


# Ведите положительное целое число: 3
# 3

# Ведите положительное целое число: 7.9
# Error type of value!

# Ведите положительное целое число: -5
# !!! x = input(-5)
# -> Допустимы только положительные числа.

У объектов класса исключений Exception и его производных, определен метод __str__() так, чтобы выводить значения атрибутов. Поэтому можно не обращаться напрямую к полям объекта: e.expression и e.message. Кроме того у экземпляров класса исключений Exception есть атрибут args. Через него можно получать доступ к отдельным полям, как показано в примере выше.

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

Настройка собственных классов исключений.

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

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

Рассмотрим пример:

class SalaryNotInRangeError(Exception):
    """Исключение возникает из-за ошибок в зарплате.

    Атрибуты:
        salary: входная зарплата, вызвавшая ошибку
        message: объяснение ошибки
    """

    def __init__(self, salary, message="Зарплата не входит в диапазон (5000, 15000)"):
        self.salary = salary
        self.message = message
        # переопределяется конструктор встроенного класса `Exception()`
        super().__init__(self.message)


salary = int(input("Введите сумму зарплаты: "))
if not 5000 < salary < 15000:
    raise SalaryNotInRangeError(salary)

В примере, для приема аргументов salary и message переопределяется конструктор встроенного класса Exception(). Затем конструктор родительского класса Exception() вызывается вручную с аргументом self.message при помощи функции super(). Пользовательский атрибут self.salary определен для использования позже.

Результаты запуска скрипта:

Введите сумму зарплаты: 2000
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    raise SalaryNotInRangeError(salary)
__main__.SalaryNotInRangeError: Зарплата не входит в диапазон (5000, 15000)

Унаследованный метод __str__ класса Exception() используется для отображения соответствующего сообщения при возникновении SalaryNotInRangeError(). Также можно настроить сам метод __str__, переопределив его.

class SalaryNotInRangeError(Exception):
    """Исключение возникает из-за ошибок в зарплате.

    Атрибуты:
        salary: входная зарплата, вызвавшая ошибку
        message: объяснение ошибки
    """

    def __init__(self, salary, message="Зарплата не входит в диапазон (5000, 15000)"):
        self.salary = salary
        self.message = message
        super().__init__(self.message)

    # переопределяем метод '__str__'
    def __str__(self):
        return f'{self.salary} -> {self.message}'

salary = int(input("Введите сумму зарплаты: "))
if not 5000 < salary < 15000:
    raise SalaryNotInRangeError(salary)

Вывод работы скрипта:

Введите сумму зарплаты: 2000
Traceback (most recent call last):
  File "test.py", line 20, in <module>
    raise SalaryNotInRangeError(salary)
__main__.SalaryNotInRangeError: 2000 -> Зарплата не входит в диапазон (5000, 15000)

Как перехватывать пользовательское исключение.

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

Если необходимо, чтобы код использовал пользовательское исключение, то сначала нужно перехватить исключение, определяемое используемым модулем, а затем повторно вызвать, при помощи raise, своё собственное исключение.

import sqlite3 

class MyError(Exception):
     """Could not connect to db"""
     pass

try:         
    conn= sqlite3.connect('database.sqlite')
except sqlite3.Error as e:
    raise MyError(f'Could not connect to db: {e.value}')

В примере ловиться исключение, определяемое модулем sqlite3, а затем вызывается пользовательское исключение при помощи raise.