Инструкция raise, принудительно поднимает исключение
Инструкция raise позволяет программисту принудительно вызвать указанное исключение. Например:
>>> raise NameError('HiThere') # Traceback (most recent call last): # File "", line 1, in # NameError: HiThere
В общем случае инструкция raise повторно вызывает последнее исключение, которое было активным в текущей области видимости. Если нужно определить, было ли вызвано исключение, но не обрабатывать его, более простая форма инструкции raise позволяет повторно вызвать исключение:
try: raise NameError('HiThere') except NameError: print('Исключение пролетело незаметно!') # Еще какие-то действия, например запись в журнал логов . # затем повторно вызываем `NameError` raise # Исключение пролетело незаметно! # Traceback (most recent call last): # File "", line 2, in # NameError: HiThere
Если в текущей области видимости нет активного исключения, то в месте, где указана инструкция raise , без указания , возникает исключение RuntimeError , указывающее на ошибку.
>>> raise # Traceback (most recent call last): # File "", line 1, in # RuntimeError: No active exception to reraise
В противном случае raise вычисляет первое выражение как объект исключения. Он должен быть подклассом BaseException , например Exception или один из его подклассов. Если передается класс исключения, он будет неявно создан путем вызова его конструктора без аргументов.
# сокращение для 'raise ValueError()' >>> raise ValueError # Traceback (most recent call last): # File "", line 1, in # ValueError
При возникновении исключения объект traceback обычно создается автоматически и присоединяется к нему в качестве атрибута __traceback__ . Следовательно можно создать исключение путем raise и установить в него свой собственный traceback за один шаг, используя метод BaseException.with_traceback() , например:
try: . except SomeException: # Получаем трассировку tb = sys.exception().__traceback__ # передаем трассировку raise AnyException(. ).with_traceback(tb)
Инструкция raise и цепочка исключений.
Если внутри раздела except (конструкции try/except ) появляется НЕперехваченное исключение (например с помощью raise ), то к нему будет привязано исключение, которое было перехвачено инструкцией except в качестве атрибута __cause__ , и оба будут выведены в сообщении об ошибке:
try: open("database.sqlite") except OSError: raise RuntimeError("не удается обработать ошибку") # Traceback (most recent call last): # File "", line 2, in # FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite' # During handling of the above exception, another exception occurred: # Traceback (most recent call last): # File "", line 4, in # RuntimeError: не удается обработать ошибку
Подобный механизм работает неявно, если исключение вызывается внутри обработчика исключений (внутри предложения try ) или внутри предложения finally , то предыдущее исключение присоединяется в качестве атрибута __context__ нового исключения:
Оператор raise допускает необязательное предложение from , которое используется для указания того, что одно исключение является прямым следствием другого:
def func(): raise ConnectionError try: func() except ConnectionError as exc: raise RuntimeError('Не удалось открыть базу данных') from exc # Traceback (most recent call last): # File "", line 2, in # File "", line 2, in func # ConnectionError # The above exception was the direct cause of the following exception: # Traceback (most recent call last): # File "", line 4, in # RuntimeError: Не удалось открыть базу данных
Цепочка исключений может быть явно подавлена/отключена путем указания значения None в предложении from :
try: a = 1 / 0 except Exception as exc: raise RuntimeError("Случилось что-то плохое") from None # Traceback (most recent call last): # File "", line 4, in # RuntimeError: Случилось что-то плохое
Пример вызова исключения, когда выполнение программы бессмысленно или не может продолжаться.
# например, поступили данные с клавиатуры s = 'apple' try: # пытаемся преобразовать данные num = int(s) except ValueError: raise ValueError("Строка не может быть преобразована в целое число") from None # Traceback (most recent call last): # File "", line 5, in # ValueError: Строка не может быть преобразована в целое число
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Обработка/перехват исключений try/except
- Инструкция finally, очистка внешних ресурсов
- Тонкости работы конструкции try/except/else/finally
- Создание пользовательского класса исключения
- Обработка группы исключений, оператор except*
- Эффективная обработка исключений
- Инструкция raise, принудительный вызов исключений
- Отладочные утверждение assert
- Улучшения сообщений об ошибках 3.10
- Улучшения сообщений об ошибках 3.11
- Улучшения сообщений об ошибках 3.12
Инструкция raise и пользовательские исключения
Мы продолжаем тему исключений. Во всех наших предыдущих примерах исключение возникало в результате ошибочных ситуаций во время работы программы, например, деления на ноль:
print("Куда ты скачешь, гордый конь,") print("И где опустишь ты копыта?") print("О мощный властелин судьбы!") 1/0 print("Не так ли ты над самой бездной") print("На высоте, уздой железной") print("Россию поднял на дыбы?")
Но как эта операция деления формирует само исключение? Для этого в языке Python имеется конструкция (оператор)
которая и порождает указанные типы исключений. В самом простом варианте, мы можем вместо деления на ноль записать этот оператор и указать тип исключения ZeroDivisionError:
raise ZeroDivisionError("Деление на ноль")
Результат выполнения программы будет тем же – она остановится на конструкции raise. Только сообщение об ошибке теперь будет на русском языке – та строка, что мы указали при формировании объекта класса ZeroDivisionError. То есть, после оператора raise мы можем прописывать нужный нам класс исключения с собственными параметрами. Также можно просто указывать класс, не прописывая каких-либо параметров:
raise ZeroDivisionError
Здесь у нас также создается экземпляр, но без параметров. Раз это так, значит, можно заранее создать экземпляр класса:
e = ZeroDivisionError("Деление на ноль")
а, затем, сгенерировать это исключение:
raise e
Вообще, мы можем использовать любой класс в качестве исключения, унаследованного от базового класса:
Например, если просто указать строку после оператора raise:
raise "деление на ноль"
то интерпретатор Python как раз это нам и укажет:
TypeError: exceptions must derive from BaseException
То есть, после raise должен находиться экземпляр класса исключения, а не какой-то произвольный объект.
Когда нам может понадобиться оператор raise? И разве сам язык Python не может генерировать нужные исключения при возникновении ошибок? Часто именно так и происходит. Например, если мы будем делать некорректные операции, вроде:
1 + "2" [1, 2, 3][4]
то автоматически возникают ошибки заданного типа. Но прописать исключения на все случаи жизни невозможно. И если в качестве примера взять все тот же класс печати данных:
То, в частности, метод send_data() может генерировать свое исключение, если по каким-то причинам данные не были отправлены принтеру. В качестве демонстрации я приведу гипотетический класс PrintData для работы с принтером:
class PrintData: def print(self, data): self.send_data(data) print(f"печать: ") def send_data(self, data): if not self.send_to_print(data): raise Exception("принтер не отвечает") def send_to_print(self, data): return False
Как раз здесь мы генерируем исключение, если данные не могут быть отправлены принтеру. Затем, это исключение может быть обработано на любом уровне стека вызова. Например, если далее создать экземпляр этого класса и вызвать метод print():
p = PrintData() p.print("123")
То мы увидим сформированное нами исключение. Как вы понимаете, в язык Python не встроена по умолчанию возможность генерации исключения при взаимодействии с принтером. Это приходится делать уже самому программисту с помощью оператора raise. Вот для этого он и нужен.
Создание пользовательских исключений
В нашем гипотетическом классе PrintData исключение генерируется с помощью класса Exception. Почему именно он? Если мы посмотрим на иерархию классов исключений языка Python, то здесь во главе стоит базовый класс BaseException:
Остальные классы наследуются от него и имеют строгую специализацию, кроме, разве что, класса Exception, который является общим для большого разнообразия типов исключений в момент выполнения программы. Так почему же мы выбрали класс Exception, а не BaseException? Дело в том, что классы SystemExit, GeneratorExit и KeyboardInterrupt являются весьма специфичными и, обычно, они не используются при обработке собственных исключений. Поэтому, целесообразно выбирать именно класс Exception для формирования новых собственных классов исключений. Что мы сейчас и сделаем.
Итак, чтобы сформировать свой новый тип исключения, нужно прописать класс, который рекомендуется наследовать от класса Exception. В самом простом варианте достаточно просто описать иерархию:
class ExceptionPrintSendData(Exception): """Класс исключения при отправке данных принтеру"""
И далее в программе использовать этот новый класс:
def send_data(self, data): if not self.send_to_print(data): raise ExceptionPrintSendData("принтер не отвечает")
Соответственно, ниже в программе, мы можем обработать этот тип ошибки, просто указав имя нашего нового класса:
p = PrintData() try: p.print("123") except ExceptionPrintSendData: print("Ошибка печати")
Видите, как это здорово! Мы создали новый тип исключения, просто прописав новый класс. И благодаря этому можем отличить ошибку передачи данных принтеру от каких-либо других ошибок.
Разумеется, мы поймаем эту же ошибку, если укажем базовый класс Exception, но пропустим, если указать какой-либо другой независимый класс исключения, иерархически не связанный с нашим, например, ArithmeticError.
Кроме того, мы можем расширить функционал класса ExceptionPrintSendData. Давайте добавим в него конструктор. Он прописывается для произвольного числа аргументов:
class ExceptionPrintSendData(Exception): """Класс исключения при отправке данных принтеру""" def __init__(self, *args): self.message = args[0] if args else None
А также магически метод__str__ для представления ошибки в консоли:
def __str__(self): return f"Ошибка: "
Если теперь убрать блок try/except и вызвать метод print(), то увидим наш вариант отображения ошибки в консоли:
p = PrintData() p.print("123")
Это лишь пример расширения функционала класса исключения. В каждом конкретном случае программист может написать любую свою реализацию.
Наконец, пользовательские классы исключений дают возможность создавать свою иерархию исключений. В частности, в нашем примере, можно прописать общий класс исключений для принтера ExceptionPrint:
class ExceptionPrint(Exception): """Общий класс исключения принтера"""
А, затем, остальные, более конкретные типы наследовать от него:
class ExceptionPrintSendData(ExceptionPrint): """Класс исключения при отправке данных принтеру"""
В результате, в блоке except мы можем отлавливать как конкретные типы ошибок, так и общие, связанные с принтером:
p = PrintData() try: p.print("123") except ExceptionPrintSendData as e: print(e) except ExceptionPrint: print("Ошибка печати")
Такой подход дает гибкий механизм обработки собственных типов исключений, благодаря чему, программа становится более понятной и структурированной.
Видео по теме
Концепция ООП простыми словами
#1. Классы и объекты. Атрибуты классов и объектов
#2. Методы классов. Параметр self
#3. Инициализатор __init__ и финализатор __del__
#4. Магический метод __new__. Пример паттерна Singleton
#5. Методы класса (classmethod) и статические методы (staticmethod)
#6. Режимы доступа public, private, protected. Сеттеры и геттеры
#7. Магические методы __setattr__, __getattribute__, __getattr__ и __delattr__
#8. Паттерн Моносостояние
#9. Свойства property. Декоратор @property
#10. Пример использования объектов property
#11. Дескрипторы (data descriptor и non-data descriptor)
#12. Магический метод __call__. Функторы и классы-декораторы
#13. Магические методы __str__, __repr__, __len__, __abs__
#14 Магические методы __add__, __sub__, __mul__, __truediv__
#15. Методы сравнений __eq__, __ne__, __lt__, __gt__ и другие
#16. Магические методы __eq__ и __hash__
#17. Магический метод __bool__ определения правдивости объектов
#18. Магические методы __getitem__, __setitem__ и __delitem__
#19. Магические методы __iter__ и __next__
#20. Наследование в объектно-ориентированном программировании
#21. Функция issubclass(). Наследование от встроенных типов и от object
#22. Наследование. Функция super() и делегирование
#23. Наследование. Атрибуты private и protected
#24. Полиморфизм и абстрактные методы
#25. Множественное наследование
#26. Коллекция __slots__
#27. Как работает __slots__ с property и при наследовании
#28. Введение в обработку исключений. Блоки try / except
#29. Обработка исключений. Блоки finally и else
#30. Распространение исключений (propagation exceptions)
#31. Инструкция raise и пользовательские исключения
#32. Менеджеры контекстов. Оператор with
#33. Вложенные классы
#34. Метаклассы. Объект type
#35. Пользовательские метаклассы. Параметр metaclass
#36. Метаклассы в API ORM Django
#37. Введение в Python Data Classes (часть 1)
#38. Введение в Python Data Classes (часть 2)
#39. Python Data Classes при наследовании
© 2023 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта
raise
Для принудительной генерации исключения используется инструкция raise.
Самый простой пример работы с raise может выглядеть так.
try: raise Exception("Some exception") except Exception as e: print("Exception exception " + str(e))
Таким образом, можно «вручную» вызывать исключения при необходимости.
Или перевызвать последнее исключение, вызвав raise без параметров.
Информация в исключении
Обычно достаточно перехватить исключение и реагировать на сам факт его появления.
args — это кортеж составных частей исключения. В него можно добавить свою информацию.
(a,b,c) = d
может породить исключение
ValueError: unpack list of wrong size
Сразу возникает вопрос — а что в переменной d, что не удалось его распаковать. Добавим значение переменной d в исключение и raise обновленное исключение.
try: a, b, c = d except Exception as e: e.args += (d,) raise
Иногда нужно логировать исключение или обрабатывать дополнительную информацию.
import traceback import sys def foo(a): x = 5 / a print(x, a) try: foo(5) foo(0) # на 0 делить нельзя foo(7) except ZeroDivisionError as e: # если e не нужно, то as e не пишем print('Поймали исключение!') print(e) # печать 'division by zero' print('-'*60) traceback.print_exc(file=sys.stdout) # печать stacktrace print('-'*60) print('После блока обработки исключений')
1.0 5 Поймали исключение! division by zero ------------------------------------------------------------ Traceback (most recent call last): File "1try.py", line 10, in foo(0) # на 0 делить нельзя File "1try.py", line 5, in foo x = 5 / a ZeroDivisionError: division by zero ------------------------------------------------------------ После блока обработки исключений
Напечатать только сообщение исключения
str(e) — просто преобразуйте исключение к строке.
Напечатать stacktrace исключения
Используйте функцию traceback.print_exc()
Тип, значение и traceback
Используйте функцию sys.exc_info()
exc_type, exc_value, exc_traceback = sys.exc_info()
Пример, как все это используется
Сохраните программу в файл tb.py и запустите ее.
import sys, traceback def lumberjack(): bright_side_of_death() def bright_side_of_death(): return tuple()[0] try: lumberjack() except IndexError: exc_type, exc_value, exc_traceback = sys.exc_info() print("*** print_tb:") traceback.print_tb(exc_traceback, limit=1, file=sys.stdout) print("*** print_exception:") # exc_type below is ignored on 3.5 and later traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2, file=sys.stdout) print("*** print_exc:") traceback.print_exc(limit=2, file=sys.stdout) print("*** format_exc, first and last line:") formatted_lines = traceback.format_exc().splitlines() print(formatted_lines[0]) print(formatted_lines[-1]) print("*** format_exception:") # exc_type below is ignored on 3.5 and later print(repr(traceback.format_exception(exc_type, exc_value, exc_traceback))) print("*** extract_tb:") print(repr(traceback.extract_tb(exc_traceback))) print("*** format_tb:") print(repr(traceback.format_tb(exc_traceback))) print("*** tb_lineno:", exc_traceback.tb_lineno)
*** print_tb: File "tb.py", line 10, in lumberjack() *** print_exception: Traceback (most recent call last): File "tb.py", line 10, in lumberjack() File "tb.py", line 4, in lumberjack bright_side_of_death() IndexError: tuple index out of range *** print_exc: Traceback (most recent call last): File "tb.py", line 10, in lumberjack() File "tb.py", line 4, in lumberjack bright_side_of_death() IndexError: tuple index out of range *** format_exc, first and last line: Traceback (most recent call last): IndexError: tuple index out of range *** format_exception: ['Traceback (most recent call last):\n', ' File "tb.py", line 10, in \n lumberjack()\n', ' File "tb.py", line 4, in lumberjack\n bright_side_of_death()\n', ' File "tb.py", line 7, in bright_side_of_death\n return tuple()[0]\n', 'IndexError: tuple index out of range\n'] *** extract_tb: [10 in >, 4 in lumberjack>, 7 in bright_side_of_death>] *** format_tb: [' File "tb.py", line 10, in \n lumberjack()\n', ' File "tb.py", line 4, in lumberjack\n bright_side_of_death()\n', ' File "tb.py", line 7, in bright_side_of_death\n return tuple()[0]\n'] *** tb_lineno: 10
results matching » «
No results matching » «
Что за конструкция raise from в Python?
Конструкция raise from в языке Python используется для того, чтобы сформировать дополнительный контекст (цепочку возникновения) исключения.
Синтаксис этой конструкции следующий: raise from
Как это работает:
У каждого исключения есть магические атрибуты: __context__ и __cause__ , которые содержат в себе контекст возникновения исключения, по умолчанию они всегда инициализируются значением None , это можно наглядно увидеть:
try: raise Exception except Exception as e: print(e.__context__) # None print(e.__cause__) # None
Но в ряде случаев данные из этих атрибутов будут использованы для вывода дополнительного контекста ошибки.
Расскажу об этом подробнее:
- Если при обработке ( except ) одного исключения вызывается другое исключение, то используется атрибут __context__ , он будет содержать ссылку на то исключение, которое возникло первым.
Убедимся в этом:
try: try: raise ValueError except ValueError: raise TypeError except TypeError as e: print(repr(e.__context__)) # ValueError()
- В случае же, когда исключение вызывается при помощи конструкции raise from используется атрибут __cause__ , значением которого становится ссылка на то исключение, которое находится после оператора from (причина исключения):
Это тоже можно увидеть:
try: try: raise ValueError except ValueError: raise TypeError from IndexError except TypeError as e: print(repr(e.__cause__)) # IndexError()
Так как эти атрибуты магические, то вся работа происходит с ними «под капотом», и в действительности запись:
raise Exception from ValueError
Полностью аналогична следующей последовательности действий:
e = Exception() e.__cause__ = ValueError() raise e
Весь дополнительный контекст исключения, который содержат в себе эти атрибуты, конечный пользователь увидит в специфичных ошибках, на которые мы сейчас посмотрим:
Случай 1:
Вызов одного исключения, во время обработки другого исключения (контекст, который содержит в себе атрибут __context__ ):
При запуске кода:
try: raise ValueError except ValueError: raise TypeError
Возникает следующая ошибка:
Traceback (most recent call last): File "test.py", line 3, in raise ValueError ValueError During handling of the above exception, another exception occurred: Traceback (most recent call last): File "test.py", line 5, in raise TypeError TypeError
В тексте ошибки можно заметить следующую строку:
During handling of the above exception, another exception occurred:
Таким образом, отображается весь необходимый контекст: изначально обрабатывалось исключение ValueError и во время его обработки возникло другое исключение TypeError .
Случай 2:
При использовании конструкции raise from (контекст, который содержит в себе атрибут __cause__ ):
try: raise ValueError except ValueError as e: raise TypeError from e
Вызывает следующую ошибку:
Traceback (most recent call last): File "C:\Users\Pavel\Desktop\random-from-sv\test.py", line 2, in raise ValueError ValueError The above exception was the direct cause of the following exception: Traceback (most recent call last): File "C:\Users\Pavel\Desktop\random-from-sv\test.py", line 4, in raise TypeError from e TypeError
В этой ситуации текст ошибки уже другой, и мы можем наблюдать пояснение о причине исключения:
The above exception was the direct cause of the following exception:
Благодаря чему мы понимаем, что исключение ValueError , которое было выброшено, возникло по причине другого исключения TypeError .
raise from None
Однако, что же это за конструкция raise from None ?
Ведь не имеет смысла явно указывать пользователю, что исключение произошло без причины.
В чем тогда будет отличие от обычного вызова raise , когда значение __cause__ инициализируется None ?
На самом деле, эта конструкция применяется для того, чтобы скрыть (подавить) остальной контекст исключения.
Посмотрим на код ниже:
try: raise ValueError except ValueError: raise TypeError from None
При его запуске мы увидим уже совсем другую ошибку:
Traceback (most recent call last): File "test.py", line 5, in raise TypeError from None TypeError
В которой не содержится никакой информации об исходном исключении ValueError — оно было скрыто.
Из-за чего так произошло?
Дело в том, что при любой перезаписи атрибута __cause__ (даже значением None ):
e.__cause__ = None
Происходит дополнительное действие:
e.__suppress_context__ = True
Этот магический атрибут __suppress_context__ отвечает за то, попадет ли в итоговый вывод значение __context__ .
В связи с чем, контекст всегда дополняется только одним значением — это либо значение атрибута __context__ , либо, в случае, если была использована конструкция raise from , значение атрибута __cause__ .
В примере кода выше, при вызове конструкции raise TypeError from None , несмотря на то, что значение __context__ содержало ссылку на исключение ValueError , которое было самым первым в цепочке, оно было проигнорировано, поскольку перезапись атрибута __cause__ (оператором from None ) установила __suppress_context__ в значение True .
Итоги
Обобщив все вышесказанное мы можем понять, что:
- Атрибут __cause__ — содержит в себе прямую ссылку на то исключение, по причине которого оно возникло (from cause).
- Атрибут __context__ — используется при множественном вызове исключений и содержит в себе прямую ссылку на то исключение, которое возникло первым.
- Атрибут __suppress_context__ — отвечает за подавление значения __context__ и используется в конструкции raise from .
И если говорить на более глобальном уровне, который виден при запуске кода:
- Конструкция raise from используется для того, чтобы сохранить дополнительный контекст исключения.
- Конструкция raise from None используется для того, чтобы показать только последнее исключение, и скрыть весь остальной контекст.
Дополнительно, можно почитать официальную документацию на этот счет.