Как создать свое исключение python
Иногда возникает необходимость вручную сгенерировать то или иное исключение. Для этого применяется оператор 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"Имя: Возраст: ")
Здесь класс 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"Недопустимое значение: . " \ f"Возраст должен быть в диапазоне от до " 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"Имя: Возраст: ") 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"Недопустимое значение: . " \ f"Возраст должен быть в диапазоне от до "
В функции __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 в виде вывода информации об исключении на консоль.
Исключения, определенные пользователем в языке 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 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 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 .
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Обработка/перехват исключений try/except
- Инструкция finally, очистка внешних ресурсов
- Тонкости работы конструкции try/except/else/finally
- Создание пользовательского класса исключения
- Обработка группы исключений, оператор except*
- Эффективная обработка исключений
- Инструкция raise, принудительный вызов исключений
- Отладочные утверждение assert
- Улучшения сообщений об ошибках 3.10
- Улучшения сообщений об ошибках 3.11
- Улучшения сообщений об ошибках 3.12
Как определять собственные классы исключений в Python
Ваш интерес к новой книге "Секреты 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, '.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(, [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 , автоматически вызываемые в процессе обработки исключений. Только от вас зависит, что именно будет делать ваш собственный класс исключений. Среди показанных методов – такие, что отвечают за инспектирование объекта и вывод на экран информативного сообщения об ошибке.
Как бы то ни было, классы исключений значительно упрощают обработку всех возникающих ошибок!
- python
- программирование
- обработка ошибок
Создание классов исключений
В Python можно создавать собственные классы исключений. Они должны напрямую или опосредованно быть потомками класса Exception .
class MyError(Exception): def __init__(self, text): self.txt = text a = input("Input positive integer: ") try: a = int(a) if a < 0: raise MyError("You give negative!") except ValueError: print("Error type of value!") except MyError as mr: print(mr) else: print(a)
Input positive integer: -10 You give negative!
В данном случае в выражении MyError("You give negative!") создается экземпляр собственного класса исключений. С помощью raise исключение возбуждается. В перехватившей его соответствующей ветке except исключение присваивается переменной mr .
У объектов класса Exception (и производных от него) определен метод __str__() так, чтобы выводить значения атрибутов. Поэтому можно не обращаться напрямую к полям объекта, например, так: mr.txt .
Кроме того у экземпляров Exception есть атрибут args . Через него можно получать доступ к отдельным полям:
class MyError(Exception): def __init__(self, text, num): self.txt = text self.n = num a = input("Input positive integer: ") try: a = int(a) if a < 0: raise MyError("You give negative!", a) except ValueError: print("Error type of value!") except MyError as mr: print(mr) print(mr.args) print(mr.args[0]) print(mr.args[1]) else: print(a)
Input positive integer: -3 ('You give negative!', -3) ('You give negative!', -3) You give negative! -3
Пример наследования от классов-исключений. При перехвате родительский класс "ловит" дочерние, но не наоборот.
class General(Exception): pass class Specific1(General): pass class Specific2(General): pass def raiser0(): x = General() raise x def raiser1(): x = Specific1() raise x def raiser2(): x = Specific2() raise x for func in (raiser0, raiser1, raiser2): try: func() except General: import sys print(sys.exc_info()[0])