Создание своих ошибок python

Время на прочтение
7 мин

Количество просмотров 27K

Привет, Хабр!

Ваш интерес к новой книге «Секреты Python Pro» убедил нас, что рассказ о необычностях Python заслуживает продолжения. Сегодня предлагаем почитать небольшой туториал о создании кастомных (в тексте — собственных) классах исключений. У автора получилось интересно, сложно не согласиться с ним в том, что важнейшим достоинством исключения является полнота и ясность выдаваемого сообщения об ошибке. Часть кода из оригинала — в виде картинок.

Добро пожаловать под кат.

Создание собственных классов ошибок

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

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

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

Собственный класс исключений MyCustomError

При выдаче исключения требуются методы __init__() и __str__().

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

В вышеприведенном классе MyCustomError есть два волшебных метода, __init__ и __str__, автоматически вызываемых в процессе обработки исключений. Метод Init вызывается при создании экземпляра, а метод str – при выводе экземпляра на экран. Следовательно, при выдаче исключения два этих метода обычно вызываются сразу друг за другом. Оператор вызова исключения в Python переводит программу в состояние ошибки.

В списке аргументов метода __init__ есть *args. Компонент *args – это особый режим сопоставления с шаблоном, используемый в функциях и методах. Он позволяет передавать множественные аргументы, а переданные аргументы хранит в виде кортежа, но при этом позволяет вообще не передавать аргументов.

В нашем случае можно сказать, что, если конструктору MyCustomError были переданы какие-либо аргументы, то мы берем первый переданный аргумент и присваиваем его атрибуту message в объекте. Если ни одного аргумента передано не было, то атрибуту message будет присвоено значение None.

В первом примере исключение MyCustomError вызывается без каких-либо аргументов, поэтому атрибуту message этого объекта присваивается значение None. Будет вызван метод str, который выведет на экран сообщение ‘MyCustomError message has been raised’.

Исключение MyCustomError выдается без каких-либо аргументов (скобки пусты). Иными словами, такая конструкция объекта выглядит нестандартно. Но это просто синтаксическая поддержка, оказываемая в Python при выдаче исключения.

Во втором примере MyCustomError передается со строковым аргументом ‘We have a problem’. Он устанавливается в качестве атрибута message у объекта и выводится на экран в виде сообщения об ошибке, когда выдается исключение.

Код для класса исключения MyCustomError находится здесь.

class MyCustomError(Exception):
    def __init__(self, *args):
        if args:
            self.message = args[0]
        else:
            self.message = None

    def __str__(self):
        print('calling str')
        if self.message:
            return 'MyCustomError, {0} '.format(self.message)
        else:
            return 'MyCustomError has been raised'


# выдача MyCustomError

raise MyCustomError('We have a problem')

Класс CustomIntFloatDic

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

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

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

Создавая собственный словарь, нужно учитывать, что в нем есть два места, где в словарь могут добавляться значения. Во-первых, это может происходить в методе init при создании объекта (на данном этапе объекту уже могут быть присвоены ключи и значения), а во-вторых — при установке ключей и значений прямо в словаре. В обоих этих местах требуется написать код, гарантирующий, что значение может относиться только к типу int или float.

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

Если создан экземпляр класса CustomIntFloatDict, причем, параметрам ключа и значения не передано никаких аргументов, то они будут установлены в None. Выражение if интерпретируется так: если или ключ равен None, или значение равно None, то с объектом будет вызван метод get_dict(), который вернет атрибут empty_dict; такой атрибут у объекта указывает на пустой список. Помните, что атрибуты класса доступны у всех экземпляров класса.

Назначение этого класса — позволить пользователю передать список или кортеж с ключами и значениями внутри. Если пользователь вводит список или кортеж в поисках ключей и значений, то два эти перебираемых множества будут сцеплены при помощи функции zip языка Python. Подцепленная переменная, указывающая на объект zip, поддается перебору, а кортежи поддаются распаковке. Перебирая кортежи, я проверяю, является ли val экземпляром класса int или float. Если val не относится ни к одному из этих классов, я выдаю собственное исключение IntFloatValueError и передаю ему val в качестве аргумента.

Класс исключений IntFloatValueError

При выдаче исключения IntFloatValueError мы создаем экземпляр класса IntFloatValueError и одновременно выводим его на экран. Это означает, что будут вызваны волшебные методы init и str.

Значение, спровоцировавшее выдаваемое исключение, устанавливается в качестве атрибута value, сопровождающего класс IntFloatValueError. При вызове волшебного метода str пользователь получает сообщение об ошибке, информирующее, что значение init в CustomIntFloatDict является невалидным. Пользователь знает, что делать для исправления этой ошибки.

Классы исключений IntFloatValueError и KeyValueConstructError

Если ни одно исключение не выдано, то есть, все val из сцепленного объекта относятся к типам int или float, то они будут установлены при помощи __setitem__(), и за нас все сделает метод из родительского класса dict, как показано ниже.

Класс KeyValueConstructError

Что произойдет, если пользователь введет тип, не являющийся списком или кортежем с ключами и значениями?

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

Если пользователь не укажет ключи и значения как список или кортеж, то будет выдано исключение KeyValueConstructError. Цель этого исключения – проинформировать пользователя, что для записи ключей и значений в объект CustomIntFloatDict, список или кортеж должен быть указан в конструкторе init класса CustomIntFloatDict.

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

Опять же, когда выдано исключение, создается экземпляр KeyValueConstructError, и при этом ключ и значения передаются в качестве аргументов конструктору KeyValueConstructError. Они устанавливаются в качестве значений атрибутов key и value у KeyValueConstructError и используются в методе __str__ для генерации информативного сообщения об ошибке при выводе сообщения на экран.

Далее я даже включаю типы данных, присущие объектам, добавленным к конструктору init – делаю это для большей ясности.

Установка ключа и значения в CustomIntFloatDict

CustomIntFloatDict наследует от dict. Это означает, что он будет функционировать в точности как словарь, везде за исключением тех мест, которые мы выберем для точечного изменения его поведения.

__setitem__ — это волшебный метод, вызываемый при установке ключа и значения в словаре. В нашей реализации setitem мы проверяем, чтобы значение относилось к типу int или float, и только после успешной проверки оно может быть установлено в словаре. Если проверка не пройдена, то можно еще раз воспользоваться классом исключения IntFloatValueError. Здесь можно убедиться, что, попытавшись задать строку ‘bad_value’ в качестве значения в словаре test_4, мы получим исключение.

Весь код к этому руководству показан ниже и выложен на Github.

# Создаем словарь, значениями которого могут служить только числа типов int и float  

class IntFloatValueError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return '{} is invalid input, CustomIntFloatDict can only accept ' \
               'integers and floats as its values'.format(self.value)


class KeyValueContructError(Exception):
    def __init__(self, key, value):
        self.key = key
        self.value = value

    def __str__(self):
        return 'keys and values need to be passed as either list or tuple' + '\n' + \
                ' {} is of type: '.format(self.key) + str(type(self.key)) + '\n' + \
                ' {} is of type: '.format(self.value) + str(type(self.value))


class CustomIntFloatDict(dict):

    empty_dict = {}

    def __init__(self, key=None, value=None):

        if key is None or value is None:
            self.get_dict()

        elif not isinstance(key, (tuple, list,)) or not isinstance(value, (tuple, list)):
            raise KeyValueContructError(key, value)
        else:
            zipped = zip(key, value)
            for k, val in zipped:
                if not isinstance(val, (int, float)):
                    raise IntFloatValueError(val)
                dict.__setitem__(self, k, val)

    def get_dict(self):
        return self.empty_dict

    def __setitem__(self, key, value):
        if not isinstance(value, (int, float)):
            raise IntFloatValueError(value)
        return dict.__setitem__(self, key, value)


# тестирование 

# test_1 = CustomIntFloatDict()
# print(test_1)
# test_2 = CustomIntFloatDict({'a', 'b'}, [1, 2])
# print(test_2)
# test_3 = CustomIntFloatDict(('x', 'y', 'z'), (10, 'twenty', 30))
# print(test_3)
# test_4 = CustomIntFloatDict(('x', 'y', 'z'), (10, 20, 30))
# print(test_4)
# test_4['r'] = 1.3
# print(test_4)
# test_4['key'] = 'bad_value'

Заключение

Если создавать собственные исключения, то работать с классом становится гораздо удобнее. В классе исключения должны быть волшебные методы init и str, автоматически вызываемые в процессе обработки исключений. Только от вас зависит, что именно будет делать ваш собственный класс исключений. Среди показанных методов – такие, что отвечают за инспектирование объекта и вывод на экран информативного сообщения об ошибке.

Как бы то ни было, классы исключений значительно упрощают обработку всех возникающих ошибок!

Генерация исключений и создание своих типов исключений

Последнее обновление: 30.01.2022

Генерация исключений и оператор raise

Иногда возникает необходимость вручную сгенерировать то или иное исключение. Для этого применяется оператор raise. Например, сгенерируем исключение

try:
    number1 = int(input("Введите первое число: "))
    number2 = int(input("Введите второе число: "))
    if number2 == 0:
        raise Exception("Второе число не должно быть равно 0")
    print("Результат деления двух чисел:", number1/number2)
except ValueError:
    print("Введены некорректные данные")
except Exception as e:
    print(e)
print("Завершение программы")

Оператору raise передается объект BaseException — в данном случае объект Exception. В конструктор
этого типа можно ему передать сообщение, которое затем можно вывести пользователю. В итоге, если number2 будет равно 0, то сработает оператор
raise, который сгенерирует исключение. В итоге управление программой перейдет к блоку except, который обрабатывает
исключения типа Exception:

Введите первое число: 1
Введите второе число: 0
Второе число не должно быть равно 0
Завершение программы

Создание своих типов исключений

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

class Person:
    def __init__(self, name, age):
        self.__name = name  # устанавливаем имя
        self.__age = age   # устанавливаем возраст

    def display_info(self):
        print(f"Имя: {self.__name}  Возраст: {self.__age}")

Здесь класс Person в конструкторе получает значения для имени и возраста и присваивает их приватным переменным name и age.
Однако при создании объекта Person мы можем передать в конструктор некорректное с точки зрения логики значение — например, отрицательное число.
Одним из способов решения данной ситуации представляет генерация исключения при передаче некорректных значений.

Итак, определим следующий код программы:

class PersonAgeException(Exception):
    def __init__(self, age, minage, maxage):
        self.age = age
        self.minage = minage
        self.maxage = maxage

    def __str__(self):
        return f"Недопустимое значение: {self.age}. " \
               f"Возраст должен быть в диапазоне от {self.minage} до {self.maxage}"


class Person:
    def __init__(self, name, age):
        self.__name = name  # устанавливаем имя
        minage, maxage = 1, 110
        if minage < age < maxage:   # устанавливаем возраст, если передано корректное значение
            self.__age = age
        else:                       # иначе генерируем исключение
            raise PersonAgeException(age, minage, maxage)

    def display_info(self):
        print(f"Имя: {self.__name}  Возраст: {self.__age}")

try:
    tom = Person("Tom", 37)
    tom.display_info()  # Имя: Tom 	Возраст: 37

    bob = Person("Bob", -23)
    bob.display_info()
except PersonAgeException as e:
    print(e)    # Недопустимое значение: -23. Возраст должен быть в диапазоне от 1 до 110

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

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

class PersonAgeException(Exception):
    def __init__(self, age, minage, maxage):
        self.age = age
        self.minage = minage
        self.maxage = maxage

    def __str__(self):
        return f"Недопустимое значение: {self.age}. " \
               f"Возраст должен быть в диапазоне от {self.minage} до {self.maxage}"

В функции __str__ определяем текстовое представление класса — по сути сообщение об ошибке.

В конструкторе класса Persoon проверяем переданное для возраста пользователя значение. И если это значение не соответствует
определенному диапазону, то генерируем исключение типа PersonAgeException:

raise PersonAgeException(age, minage, maxage)

При применении класса Person нам следует учитывать, что конструктор класса может сгенерировать исключение при передаче некорректного
значения. Поэтому создание объектов Person обертывается в конструкцию try..except:

try:
    tom = Person("Tom", 37)
    tom.display_info()  # Имя: Tom 	Возраст: 37

    bob = Person("Bob", -23)  # генерируется исключение типа PersonAgeException
    bob.display_info()
except PersonAgeException as e:
    print(e)    # Недопустимое значение: -23. Возраст должен быть в диапазоне от 1 до 110

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

  1. Create a Custom Exception Class in Python
  2. Execute Exception-Handling Using the try...except Block in Python

Create Custom Exceptions in Python

This tutorial will demonstrate you can create custom exception classes in Python. Here, we’ll show how you can properly perform exception handling, define custom exception classes, and override existing built-in exceptions.

Exceptions are a type of event that occurs whenever something within a program doesn’t go as intended or disrupts the flow of the intended use-case of the program. Without exception handling, the program will cease to execute entirely, and the exception would have to either be fixed or handled.

Create a Custom Exception Class in Python

Creating an Exception Class in Python is done the same way as a regular class. The main difference is you have to include the Python’s base Exception class to inform the compiler that the class you’re making is an exception class.

Let’s test this method out to create an exception class called DemoException and use the placeholder control flow keyword pass inside as a placeholder.

class DemoException(Exception):
    pass

Execute Exception-Raising Using the Keyword raise in Python

To test the DemoException class and see what it displays when it’s actually triggered, perform exception raising. Exception-raising is synonymous with exception-throwing in other programming languages.

Using the keyword raise, trigger an exception using the given exception class and outputs an exception message.

class DemoException(Exception):
    pass

raise DemoException

Output:

Traceback (most recent call last):
  File "/Users/demo/python/demo_exception.py", line 4, in <module>
    raise DemoException
__main__.DemoException

A standard exception will look like in the terminal if no custom exception message has been declared.

Declare a Custom Exception Message in Python

To declare a custom exception message for DemoException, override the __init__() method of the exception class and include the message that should be outputted for the exception in the parameters, along with the mandatory self-referential parameter self.

For example, let’s override the __init__() method and create a custom message for the DemoException class:

class DemoException(Exception):
    def __init__(self, message):
        super().__init__(message)

Take note that for the message to be integrated into your exception successfully, call the base Exception class, __init__() method, and include the message as an argument.

Let’s call the exception class again using the raise keyword, and now, passing a custom message with it:

class DemoException(Exception):
    def __init__(self, message):
        super().__init__(message)
        
message = "Exception Triggered! Something went wrong."
raise DemoException(message)

The output should look like this:

Traceback (most recent call last):
  File "/Users/demo/python/helloworld.py", line 6, in <module>
    raise DemoException(message)
__main__.DemoException: Exception Triggered! Something went wrong.

We’ve now successfully created and triggered an exception class with a custom error message.

For actual situations that may trigger an exception, how do we handle and raise these exceptions? You can solve this problem neatly by implementing exception-handling using the try...except block.

Execute Exception-Handling Using the try...except Block in Python

The try...except block is much like the try-catch block in other languages like Java.

The try...except block has 2 main blocks and 2 optional blocks:

  • try (required) — The main block responsible for encapsulating the code block where the exception might be triggered. The try block halts the whole process within it whenever an exception is triggered.
  • except (required) — The block program proceeds whenever a specified exception is triggered. This block typically contains a descriptive error message for the caller or just a simple print() statement. There may be more than one except block in a single try block, each one catching different exceptions.
  • else (optional) — This optional block is where the program will proceed if the try block did not trigger any exceptions.
  • finally (optional) — This optional block runs once everything from the previous 3 blocks has been performed regardless if an exception is triggered or not.

Let’s use the previous example using the DemoException class to try a simple try...except block.

First, wrap the raise keyword in a function and put it inside the try...except block.

The function that we’ll create for this example is a function that accepts a number and throws an exception if it sends 0. If it sends any other number, then the code will proceed as intended. Check the example below:

class DemoException(Exception):
    def __init__(self, message):
        super().__init__(message)
        

message = "Exception Triggered! Something went wrong."

def triggerException(num):
    if (num == 0):
        raise DemoException(message)
    else:
        print(num)


try:
    triggerException(0)
    print("Code has successfully been executed.")
except DemoException:
    print("Error: Number should not be 0.")

Since the triggerException() passed 0 as an argument, the code should trigger DemoException. Here we should expect the raise keyword message to be overridden with whatever is inside the except block as the output.

Notice that the print() line after the triggerException() function call was not outputted. It’s because the function raised an exception; therefore, it immediately halted all the processes within the try block and proceeded directly to the except block.

Output:

Error: Number should not be 0.

Now, let’s try passing a valid number like 20, for example.

try:
    triggerException(20)
    print("Code has successfully been executed.")
except DemoException:
    print("Error: Number should not be 0.")

Output:

20
Code has successfully been executed.

Let’s try chaining the except blocks and create another exception. Let’s call the new exception NumberFormatException, which triggers if the given input is not a number. For this exception class, let’s declare the message inside the class.

class NumberFormatException(Exception, value):
    message = f'{value} is not a number'
    def __init__(self):
        super().__init__(message)

Now, modify the code above to handle the new exception class NumberFormatException:

class DemoException(Exception):
    def __init__(self, message):
        super().__init__(message)
        
class NumberFormatException(Exception):
    def __init__(self, message, value):
        message = f'{value} is not a number'
        super().__init__(message)
        
message = "Exception occured."

def triggerException(num):
    if (not num.isdigit()):
        raise NumberFormatException(message, num)
    elif (num == 0):
        raise DemoException(message)
    else:
        print(num)

num = "sample string"
try:
    triggerException(num)
    print("Code has successfully been executed.")
except DemoException:
    print("Error: Number should not be 0.")
except NumberFormatException:
    print(num+" is not a number.")

In this code, the value of num that was passed to triggerException() is a string 'sample string' so the NumberFormatException should be triggered.

Output:

sample string is not a number.

In summary, creating custom exceptions in Python is as simple as creating a new class, but with the Exception class as an extra argument in the class definition. The raise keyword is used to trigger exceptions given the Exception Class. The try...except blocks are used to wrap one or more exceptions within a code block and modify what the code does when handling that exception and not just shutting down the program entirely.

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

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

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

Создание собственных исключений

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

>>> class CustomError(Exception):
...     pass
...

>>> raise CustomError
Traceback (most recent call last):
...
__main__.CustomError

>>> raise CustomError("An error occurred")
Traceback (most recent call last):
...
__main__.CustomError: An error occurred

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

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

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

Пример: Определяемое пользователем исключение

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

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

# define Python user-defined exceptions
class Error(Exception):
    """Base class for other exceptions"""
    pass


class ValueTooSmallError(Error):
    """Raised when the input value is too small"""
    pass


class ValueTooLargeError(Error):
    """Raised when the input value is too large"""
    pass


# you need to guess this number
number = 10

# user guesses a number until he/she gets it right
while True:
    try:
        i_num = int(input("Enter a number: "))
        if i_num < number:
            raise ValueTooSmallError
        elif i_num > number:
            raise ValueTooLargeError
        break
    except ValueTooSmallError:
        print("This value is too small, try again!")
        print()
    except ValueTooLargeError:
        print("This value is too large, try again!")
        print()

print("Congratulations! You guessed it correctly.")

Вот пример запуска этой программы:

Enter a number: 12
This value is too large, try again!

Enter a number: 0
This value is too small, try again!

Enter a number: 8
This value is too small, try again!

Enter a number: 10
Congratulations! You guessed it correctly.

Мы определили базовый класс под названием Error.

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

Настройка классов исключений

Мы можем дополнительно настроить этот класс, чтобы принимать другие аргументы в соответствии с нашими потребностями.

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

Давайте посмотрим на один пример:

class SalaryNotInRangeError(Exception):
    """Exception raised for errors in the input salary.

    Attributes:
        salary -- input salary which caused the error
        message -- explanation of the error
    """

    def __init__(self, salary, message="Salary is not in (5000, 15000) range"):
        self.salary = salary
        self.message = message
        super().__init__(self.message)


salary = int(input("Enter salary amount: "))
if not 5000 < salary < 15000:
    raise SalaryNotInRangeError(salary)

Выход

Enter salary amount: 2000
Traceback (most recent call last):
  File "<string>", line 17, in <module>
    raise SalaryNotInRangeError(salary)
__main__.SalaryNotInRangeError: Salary is not in (5000, 15000) range

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

Пользовательский атрибут self.salary определен для использования позже.

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

Мы также можем настроить сам метод __str__, переопределив его.

class SalaryNotInRangeError(Exception):
    """Exception raised for errors in the input salary.

    Attributes:
        salary -- input salary which caused the error
        message -- explanation of the error
    """

    def __init__(self, salary, message="Salary is not in (5000, 15000) range"):
        self.salary = salary
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f'{self.salary} -> {self.message}'


salary = int(input("Enter salary amount: "))
if not 5000 < salary < 15000:
    raise SalaryNotInRangeError(salary)

Выход

Enter salary amount: 2000
Traceback (most recent call last):
  File "/home/bsoyuj/Desktop/Untitled-1.py", line 20, in <module>
    raise SalaryNotInRangeError(salary)
__main__.SalaryNotInRangeError: 2000 -> Salary is not in (5000, 15000) range

47810cookie-checkКак определять пользовательские исключения в 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.

Понравилась статья? Поделить с друзьями:
  • Создание отчета об ошибке xiaomi что это
  • Содержание серий сериала врачебная ошибка чем закончится
  • Согласно многолетних наблюдений морфологическая ошибка
  • Создание нормативной базы для организации собственников жилья ошибка
  • Соединить крепко допущена лексическая ошибка все правильно