Исключения в языках программирования
Исключениями (exceptions) в языках программирования называют проблемы, возникающие в ходе выполнения программы, которые допускают возможность дальнейшей ее работы в рамках основного алгоритма. Типичным примером исключения является деление на ноль, невозможность считать данные из файла (устройства), отсутствие доступной памяти, доступ к закрытой области памяти и т.п. Для обработки таких ситуаций в языках программирования, как правило, предусматривается специальный механизм, который называется обработка исключений (exception handling).
Исключения разделяют на синхронные и асинхронные. Синхронные исключения могут возникнуть только в определенных местах программы. Например, если у вас есть код, который открывает файл и считывает из него данные, то исключение типа “ошибка чтения данных” может произойти только в указанном куске кода. Асинхронные исключения могут возникнуть в любой момент работы программы, они, как правило, связаны с какими-либо аппаратными проблемами, либо приходом данных. В качестве примера можно привести сигнал отключения питания.
В языках программирования чаще всего предусматривается специальный механизм обработки исключений. Обработка может быть с возвратом, когда после обработки исключения выполнение программы продолжается с того места, где оно возникло. И обработка без возврата, в этом случае, при возникновении исключения, осуществляется переход в специальный, заранее подготовленный, блок кода.
Различают структурную и неструктурную обработку исключений. Неструктурная обработка предполагает регистрацию функции обработчика для каждого исключения, соответственно данная функция будет вызвана при возникновении конкретного исключения. Для структурной обработки язык программирования должен поддерживать специальные синтаксические конструкции, которые позволяют выделить код, который необходимо контролировать и код, который нужно выполнить при возникновении исключительной ситуации.
Ошибки и исключения в Python
В Python выделяют два различных вида ошибок: синтаксические ошибки и исключения.
Синтаксические ошибки в Python
Синтаксические ошибки возникают в случае если программа написана с нарушениями требований Python к синтаксису. Определяются они в процессе парсинга программы. Ниже представлен пример с ошибочным написанием функции print.
>>> for i in range(10): prin("hello!") Traceback (most recent call last): File "<pyshell#2>", line 2, in <module> prin("hello!") NameError: name 'prin' is not defined
Исключения в Python
Второй вид ошибок – это исключения. Они возникают в случае если синтаксически программа корректна, но в процессе выполнения возникает ошибка (деление на ноль и т.п.). Более подробно про понятие исключения написано выше, в разделе “исключения в языках программирования”.
Пример исключения ZeroDivisionError, которое возникает при делении на 0.
>>> a = 10 >>> b = 0 >>> c = a / b Traceback (most recent call last): File "<pyshell#5>", line 1, in <module> c = a / b ZeroDivisionError: division by zero
В Python исключения являются определенным типом данных, через который пользователь (программист) получает информацию об ошибке. Если в коде программы исключение не обрабатывается, то приложение останавливается и в консоли печатается подробное описание произошедшей ошибки с указанием места в программе, где она произошла и тип этой ошибки.
Иерархия исключений в Python
Существует довольно большое количество встроенных типов исключений в языке Python, все они составляют определенную иерархию, которая выглядит так, как показано ниже.
BaseException
+– SystemExit
+– KeyboardInterrupt
+– GeneratorExit
+– Exception
+– StopIteration
+– StopAsyncIteration
+– ArithmeticError
| +– FloatingPointError
| +– OverflowError
| +– ZeroDivisionError
+– AssertionError
+– AttributeError
+– BufferError
+– EOFError
+– ImportError
+– ModuleNotFoundError
+– LookupError
| +– IndexError
| +– KeyError
+– MemoryError
+– NameError
| +– UnboundLocalError
+– OSError
| +– BlockingIOError
| +– ChildProcessError
| +– ConnectionError
| | +– BrokenPipeError
| | +– ConnectionAbortedError
| | +– ConnectionRefusedError
| | +– ConnectionResetError
| +– FileExistsError
| +– FileNotFoundError
| +– InterruptedError
| +– IsADirectoryError
| +– NotADirectoryError
| +– PermissionError
| +– ProcessLookupError
| +– TimeoutError
+– ReferenceError
+– RuntimeError
| +– NotImplementedError
| +– RecursionError
+– SyntaxError
| +– IndentationError
| +– TabError
+– SystemError
+– TypeError
+– ValueError
| +– UnicodeError
| +– UnicodeDecodeError
| +– UnicodeEncodeError
| +– UnicodeTranslateError
+– Warning
+– DeprecationWarning
+– PendingDeprecationWarning
+– RuntimeWarning
+– SyntaxWarning
+– UserWarning
+– FutureWarning
+– ImportWarning
+– UnicodeWarning
+– BytesWarning
+– ResourceWarning
Как видно из приведенной выше схемы, все исключения являются подклассом исключения BaseException. Более подробно об иерархии исключений и их описании можете прочитать здесь.
Как устроен механизм исключений
В Python есть встроенные исключения, которые появляются после того как приложение находит ошибку. В этом случае текущий процесс временно приостанавливается и передает ошибку на уровень вверх до тех пор, пока она не будет обработано. Если ошибка не будет обработана, программа прекратит свою работу (а в консоли мы увидим Traceback с подробным описанием ошибки).
Пример: напишем скрипт, в котором функция ожидает число, а мы передаём сроку (это вызовет исключение “TypeError”):
print("start") try: val = int(input("input number: ")) tmp = 10 / val print(tmp) except Exception as e: print("Error! " + str(e)) print("stop")
Пример: напишем скрипт, в котором функция ожидает число, а мы передаём сроку (это вызовет исключение “TypeError”):
def b(value):
print("-> b")
print(value + 1) # ошибка тут
def a(value):
print("-> a")
b(value)
a("10")
> -> a
> -> b
> Traceback (most recent call last):
> File "test.py", line 11, in <module>
> a("10")
> File "test.py", line 8, in a
> b(value)
> File "test.py", line 3, in b
> print(value + 1)
> TypeError: can only concatenate str (not "int") to str
В данном примере мы запускаем файл “test.py” (через консоль). Вызывается функция “a”, внутри которой вызывается функция “b”.
Все работает хорошо до точки print(value + 1)
.
Тут интерпретатор понимает, что нельзя конкатенировать строку с числом, останавливает выполнение программы и вызывает исключение “TypeError”.
Далее ошибка передается по цепочке в обратном направлении: “b” → “a” → “test.py”. Так как в данном примере мы не позаботились обработать эту ошибку, вся информация по ошибке отобразится в консоли в виде Traceback.
Traceback (трассировка) — это отчёт, содержащий вызовы функций, выполненные в определенный момент. Трассировка помогает узнать, что пошло не так и в каком месте это произошло.
Traceback лучше читать снизу вверх ↑
В нашем примере Traceback
содержится следующую информацию (читаем снизу вверх):
TypeError
— тип ошибки (означает, что операция не может быть выполнена с переменной этого типа);can only concatenate str (not "int") to str
— подробное описание ошибки (конкатенировать можно только строку со строкой);- Стек вызова функций (1-я линия — место, 2-я линия — код). В нашем примере видно, что в файле “test.py” на 11-й линии был вызов функции “a” со строковым аргументом “10”. Далее был вызов функции “b”.
print(value + 1)
это последнее, что было выполнено — тут и произошла ошибка. most recent call last
— означает, что самый последний вызов будет отображаться последним в стеке (в нашем примере последним выполнилсяprint(value + 1)
).
В Python ошибку можно перехватить, обработать, и продолжить выполнение программы — для этого используется конструкция try ... except ...
.
Например, вот как можно обработать ошибку деления на ноль:
try:
a = 7 / 0
except:
print('Ошибка! Деление на 0')
Здесь в блоке try
находится код a = 7 / 0
— при попытке его выполнить возникнет исключение и выполнится код в блоке except
(то есть будет выведено сообщение “Ошибка! Деление на 0”). После этого программа продолжит свое выполнение.
💭 PEP 8 рекомендует, по возможности, указывать конкретный тип исключения после ключевого слова except
(чтобы перехватывать и обрабатывать конкретные исключения):
try:
a = 7 / 0
except ZeroDivisionError:
print('Ошибка! Деление на 0')
Однако если вы хотите перехватывать все исключения, которые сигнализируют об ошибках программы, используйте тип исключения Exception
:
try:
a = 7 / 0
except Exception:
print('Любая ошибка!')
As — сохраняет ошибку в переменную
Перехваченная ошибка представляет собой объект класса, унаследованного от “BaseException”. С помощью ключевого слова as
можно записать этот объект в переменную, чтобы обратиться к нему внутри блока except
:
try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(e)
> [Errno 2] No such file or directory: 'ok123.txt'
В примере выше мы обращаемся к объекту класса “FileNotFoundError” (при выводе на экран через print
отобразится строка с полным описанием ошибки).
У каждого объекта есть поля, к которым можно обращаться (например если нужно логировать ошибку в собственном формате):
import datetime
now = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S")
try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(f"{now} [FileNotFoundError]: {e.strerror}, filename: {e.filename}")
> 20-11-2021 18:42:01 [FileNotFoundError]: No such file or directory, filename: ok123.txt
Finally — выполняется всегда
При обработке исключений можно после блока try
использовать блок finally
. Он похож на блок except
, но команды, написанные внутри него, выполняются обязательно. Если в блоке try
не возникнет исключения, то блок finally
выполнится так же, как и при наличии ошибки, и программа возобновит свою работу.
Обычно try/except
используется для перехвата исключений и восстановления нормальной работы приложения, а try/finally
для того, чтобы гарантировать выполнение определенных действий (например, для закрытия внешних ресурсов, таких как ранее открытые файлы).
В следующем примере откроем файл и обратимся к несуществующей строке:
file = open('ok.txt', 'r')
try:
lines = file.readlines()
print(lines[5])
finally:
file.close()
if file.closed:
print("файл закрыт!")
> файл закрыт!
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> print(lines[5])
> IndexError: list index out of range
Даже после исключения “IndexError”, сработал код в секции finally
, который закрыл файл.
p.s. данный пример создан для демонстрации, в реальном проекте для работы с файлами лучше использовать менеджер контекста with.
Также можно использовать одновременно три блока try/except/finally
. В этом случае:
- в
try
— код, который может вызвать исключения; - в
except
— код, который должен выполниться при возникновении исключения; - в
finally
— код, который должен выполниться в любом случае.
def sum(a, b):
res = 0
try:
res = a + b
except TypeError:
res = int(a) + int(b)
finally:
print(f"a = {a}, b = {b}, res = {res}")
sum(1, "2")
> a = 1, b = 2, res = 3
Else — выполняется когда исключение не было вызвано
Иногда нужно выполнить определенные действия, когда код внутри блока try
не вызвал исключения. Для этого используется блок else
.
Допустим нужно вывести результат деления двух чисел и обработать исключения в случае попытки деления на ноль:
b = int(input('b = '))
c = int(input('c = '))
try:
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
else:
print(f"a = {a}")
> b = 10
> c = 1
> a = 10.0
В этом случае, если пользователь присвоит переменной “с” ноль, то появится исключение и будет выведено сообщение “‘Ошибка! Деление на 0′”, а код внутри блока else
выполняться не будет. Если ошибки не будет, то на экране появятся результаты деления.
Несколько блоков except
В программе может возникнуть несколько исключений, например:
- Ошибка преобразования введенных значений к типу
float
(“ValueError”); - Деление на ноль (“ZeroDivisionError”).
В Python, чтобы по-разному обрабатывать разные типы ошибок, создают несколько блоков except
:
try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
except ValueError:
print('Число введено неверно')
else:
print(f"a = {a}")
> b = 10
> c = 0
> Ошибка! Деление на 0
> b = 10
> c = питон
> Число введено неверно
Теперь для разных типов ошибок есть свой обработчик.
Несколько типов исключений в одном блоке except
Можно также обрабатывать в одном блоке except сразу несколько исключений. Для этого они записываются в круглых скобках, через запятую сразу после ключевого слова except
. Чтобы обработать сообщения “ZeroDivisionError” и “ValueError” в одном блоке записываем их следующим образом:
try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except (ZeroDivisionError, ValueError) as er:
print(er)
else:
print('a = ', a)
При этом переменной er
присваивается объект того исключения, которое было вызвано. В результате на экран выводятся сведения о конкретной ошибке.
Raise — самостоятельный вызов исключений
Исключения можно генерировать самостоятельно — для этого нужно запустить оператор raise
.
min = 100
if min > 10:
raise Exception('min must be less than 10')
> Traceback (most recent call last):
> File "test.py", line 3, in <module>
> raise Exception('min value must be less than 10')
> Exception: min must be less than 10
Перехватываются такие сообщения точно так же, как и остальные:
min = 100
try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')
> Моя ошибка
Кроме того, ошибку можно обработать в блоке except
и пробросить дальше (вверх по стеку) с помощью raise
:
min = 100
try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')
raise
> Моя ошибка
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> raise Exception('min must be less than 10')
> Exception: min must be less than 10
Как пропустить ошибку
Иногда ошибку обрабатывать не нужно. В этом случае ее можно пропустить с помощью pass
:
try:
a = 7 / 0
except ZeroDivisionError:
pass
Исключения в lambda функциях
Обрабатывать исключения внутри lambda функций нельзя (так как lambda записывается в виде одного выражения). В этом случае нужно использовать именованную функцию.
20 типов встроенных исключений в Python
Иерархия классов для встроенных исключений в Python выглядит так:
BaseException
SystemExit
KeyboardInterrupt
GeneratorExit
Exception
ArithmeticError
AssertionError
...
...
...
ValueError
Warning
Все исключения в Python наследуются от базового BaseException
:
SystemExit
— системное исключение, вызываемое функциейsys.exit()
во время выхода из приложения;KeyboardInterrupt
— возникает при завершении программы пользователем (чаще всего при нажатии клавиш Ctrl+C);GeneratorExit
— вызывается методомclose
объектаgenerator
;Exception
— исключения, которые можно и нужно обрабатывать (предыдущие были системными и их трогать не рекомендуется).
От Exception
наследуются:
1 StopIteration
— вызывается функцией next в том случае если в итераторе закончились элементы;
2 ArithmeticError
— ошибки, возникающие при вычислении, бывают следующие типы:
FloatingPointError
— ошибки при выполнении вычислений с плавающей точкой (встречаются редко);OverflowError
— результат вычислений большой для текущего представления (не появляется при операциях с целыми числами, но может появиться в некоторых других случаях);ZeroDivisionError
— возникает при попытке деления на ноль.
3 AssertionError
— выражение, используемое в функции assert
неверно;
4 AttributeError
— у объекта отсутствует нужный атрибут;
5 BufferError
— операция, для выполнения которой требуется буфер, не выполнена;
6 EOFError
— ошибка чтения из файла;
7 ImportError
— ошибка импортирования модуля;
8 LookupError
— неверный индекс, делится на два типа:
IndexError
— индекс выходит за пределы диапазона элементов;KeyError
— индекс отсутствует (для словарей, множеств и подобных объектов);
9 MemoryError
— память переполнена;
10 NameError
— отсутствует переменная с данным именем;
11 OSError
— исключения, генерируемые операционной системой:
ChildProcessError
— ошибки, связанные с выполнением дочернего процесса;ConnectionError
— исключения связанные с подключениями (BrokenPipeError, ConnectionResetError, ConnectionRefusedError, ConnectionAbortedError);FileExistsError
— возникает при попытке создания уже существующего файла или директории;FileNotFoundError
— генерируется при попытке обращения к несуществующему файлу;InterruptedError
— возникает в том случае если системный вызов был прерван внешним сигналом;IsADirectoryError
— программа обращается к файлу, а это директория;NotADirectoryError
— приложение обращается к директории, а это файл;PermissionError
— прав доступа недостаточно для выполнения операции;ProcessLookupError
— процесс, к которому обращается приложение не запущен или отсутствует;TimeoutError
— время ожидания истекло;
12 ReferenceError
— попытка доступа к объекту с помощью слабой ссылки, когда объект не существует;
13 RuntimeError
— генерируется в случае, когда исключение не может быть классифицировано или не подпадает под любую другую категорию;
14 NotImplementedError
— абстрактные методы класса нуждаются в переопределении;
15 SyntaxError
— ошибка синтаксиса;
16 SystemError
— сигнализирует о внутренне ошибке;
17 TypeError
— операция не может быть выполнена с переменной этого типа;
18 ValueError
— возникает когда в функцию передается объект правильного типа, но имеющий некорректное значение;
19 UnicodeError
— исключение связанное с кодирование текста в unicode
, бывает трех видов:
UnicodeEncodeError
— ошибка кодирования;UnicodeDecodeError
— ошибка декодирования;UnicodeTranslateError
— ошибка переводаunicode
.
20 Warning
— предупреждение, некритическая ошибка.
💭 Посмотреть всю цепочку наследования конкретного типа исключения можно с помощью модуля inspect
:
import inspect
print(inspect.getmro(TimeoutError))
> (<class 'TimeoutError'>, <class 'OSError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)
📄 Подробное описание всех классов встроенных исключений в Python смотрите в официальной документации.
Как создать свой тип Exception
В Python можно создавать свои исключения. При этом есть одно обязательное условие: они должны быть потомками класса Exception
:
class MyError(Exception):
def __init__(self, text):
self.txt = text
try:
raise MyError('Моя ошибка')
except MyError as er:
print(er)
> Моя ошибка
С помощью try/except
контролируются и обрабатываются ошибки в приложении. Это особенно актуально для критически важных частей программы, где любые “падения” недопустимы (или могут привести к негативным последствиям). Например, если программа работает как “демон”, падение приведет к полной остановке её работы. Или, например, при временном сбое соединения с базой данных, программа также прервёт своё выполнение (хотя можно было отловить ошибку и попробовать соединиться в БД заново).
Вместе с try/except
можно использовать дополнительные блоки. Если использовать все блоки описанные в статье, то код будет выглядеть так:
try:
# попробуем что-то сделать
except (ZeroDivisionError, ValueError) as e:
# обрабатываем исключения типа ZeroDivisionError или ValueError
except Exception as e:
# исключение не ZeroDivisionError и не ValueError
# поэтому обрабатываем исключение общего типа (унаследованное от Exception)
# сюда не сходят исключения типа GeneratorExit, KeyboardInterrupt, SystemExit
else:
# этот блок выполняется, если нет исключений
# если в этом блоке сделать return, он не будет вызван, пока не выполнился блок finally
finally:
# этот блок выполняется всегда, даже если нет исключений else будет проигнорирован
# если в этом блоке сделать return, то return в блоке
Подробнее о работе с исключениями в Python можно ознакомиться в официальной документации.
Рассмотрим ещё пример:
Преобразование строки в число:
string = "5" number = int(string) print(number)
Данный скрипт успешно скомпилируется и выполнится, так как строка “5” вполне может быть конвертирована в число. Однако возьмем другой пример:
string = "hello" number = int(string) print(number)
При выполнении этого скрипта будет выброшено исключение ValueError, так как строку “hello” нельзя преобразовать в число:
Traceback (most recent call last): File "C:\Python\1.py", line 2, in <module> number = int(string) ValueError: invalid literal for int() with base 10: 'hello'
С одной стороны, здесь очевидно, что строка не представляет число, но мы можем иметь дело с вводом пользователя, который также может ввести не совсем то, что мы ожидаем:
string = input("Введите число: ") number = int(string) print(number)
При возникновении исключения работа программы прерывается, и чтобы избежать подобного поведения и обрабатывать исключения в Python есть конструкция try..except.
try..except
Конструкция try..except имеет следующее формальное определение:
(не забываем про отступы).
try:
инструкции
except [Тип_исключения]:
инструкции
Весь основной код, в котором потенциально может возникнуть исключение, помещается после ключевого слова try. Если в этом коде генерируется исключение, то работа кода в блоке try прерывается, и выполнение переходит в блок except.
После ключевого слова except опционально можно указать, какое исключение будет обрабатываться (например, ValueError или KeyError). После слова except на следующей стоке идут инструкции блока except, выполняемые при возникновении исключения.
Рассмотрим обработку исключения на примере преобразовании строки в число:
try:
number = int(input(“Введите число: “))
print(“Введенное число:”, number)
except:
print(“Преобразование прошло неудачно”)
print(“Завершение программы”)
Вводим строку: ттт
Получаем результат:
Введите число: ттт
Преобразование прошло неудачно
Завершение программы
Как видно из консольного вывода, при вводе строки вывод числа на консоль не происходит, а выполнение программы переходит к блоку except.
Вводим правильное число:
Введите число: 22
Введенное число: 22
Завершение программы
Теперь все выполняется нормально, исключение не возникает, и соответственно блок except не выполняется.
Блок finally
При обработке исключений также можно использовать необязательный блок finally. Отличительной особенностью этого блока является то, что он выполняется вне зависимости, было ли сгенерировано исключение:
try:
number = int(input(“Введите число: “))
print(“Введенное число:”, number)
except:
print(“Преобразование прошло неудачно”)
finally:
print(“Блок try завершил выполнение”)
print(“Завершение программы”)
Как правило, блок finally применяется для освобождения используемых ресурсов, например, для закрытия файлов.
Дополнительное занятие:
На этом занятии мы с вами сделаем введение в очень важную тему – исключения. Помните, когда мы говорили о некоторых методах, например, удаление элемента из пустого списка:
a=[]; a.pop()
то у нас он вызывал ошибку IndexError, т.к. удалять было уже нечего. Или же, более простой пример, деление на ноль:
5/0
также приведет к ошибке ZeroDivisionError. Или такие операции:
int("12abc") #ValueError "2"+5 #TypeError
И таких ситуаций масса. Все эти ошибки есть не что иное, как возникновение исключительных ситуаций, или попросту – исключений. Вот обработку таких стандартных исключений мы здесь и рассмотрим.
Давайте вначале предположим, что у нас имеется вот такая программа для вычисления деления двух целых чисел:
x = input("x: ") y = input("y: ") x = int(x) y = int(y) res = x/y print(res)
Если мы будем вводить целые числа, то она работает корректно, но если ввести в качестве игрека ноль или не числовую величину, то возникнут соответствующие исключения. Так вот, в Python есть механизм для отслеживания (отлавливания) таких моментов в работе программы. Для этого критический по выполнению код следует поместить в блок try:
try: x = int(x) y = int(y) res = x/y except ZeroDivisionError: res = "деление на ноль" print(res)
А после него записать except и название исключения, которое требуется отследить. В данном случае мы отлавливаем деление на ноль, т.е. исключение с именем ZeroDivisionError. Давайте запустим эту программу и посмотрим как она теперь будет работать. Вводим 1 и 0, программа теперь не завершается аварийно и в консоли видим сообщение «деление на ноль».
Более подробно блок try except работает так. Сначала идет выполнение программы внутри блока try. Если все проходит в штатном режиме, то выполнение доходит до блока except и он пропускается, не выполняется. И далее уже вызывается функция print и печатается полученный результат. Если же в процессе выполнения программы блока try возникает какое-либо исключение (любое), то выполнение программы прерывается и управление передается блоку except с соответствующим именем исключения. Если нужное имя в блоке except отсутствует, то исключение переходит на более высокий уровень (в данном случае к среде выполнения Python). И в случае отсутствия обработчика исключение считается необработанным (unhandled exception) и программа завершается аварийно.
Чтобы отловить в блоке try несколько различных исключений, их можно указать в круглых скобках через запятую после ключевого слова except:
except (ZeroDivisionError, ValueError): res = "деление на ноль или нечисловое значение"
Или, для раздельной обработки, в разных блоках except:
except ZeroDivisionError: res = "деление на ноль" except ValueError: res = "одно из введенных значений не число"
Если мы хотим при возникновении ошибок указывать служебное сообщение, записанное в соответствующих исключениях, то это делается так:
except ZeroDivisionError as z: res = z except ValueError as v: res = v
То есть, после имени исключения (в действительности, это класс, но мы о них пока еще не говорили, поэтому будем воспринимать его просто как имя) ставится ключевое слово as и дальше переменная, которая будет ссылаться на класс ValueError, в котором хранится служебное сообщение о конкретной ошибке. Выводя его с помощью print, мы в консоли видим это сообщение.
Далее, блок try поддерживает необязательный блок else, который выполняется при штатном выполнении кода внутри блока try, то есть, когда не произошло никаких ошибок. Например, его можно записать так:
else: print("Исключений не произошло")
Теперь, при запуске программы, вводя корректные числа, мы увидим это сообщение. Если же возникает любое исключение, то этот блок не выполняется.
Другим необязательным блоком является блок finally, который, наоборот, выполняется всегда после блока try, вне зависимости произошла ошибка или нет:
finally: print("Блок finally выполняется всегда")
Теперь, при запуске программы, мы всегда будем видеть это сообщение. И здесь часто возникает вопрос: зачем нужен этот блок, если он выполняется всегда после try? Мы с таким же успехом можем записать этот print сразу после этого блока и, вроде бы, все будет работать также? В действительности, нет. Смотрите, если мы, например, уберем блок except с исключением ValueError, запустим программу и введем нечисловые значения, то, конечно, возникнет необработанное исключение, но при этом, блок finally все равно выполнился! Этого не произошло бы, если просто записать print после try.
Или, вот такой пример:
def getValues(): x = input("x: ") y = input("y: ") try: x = int(x) y = int(y) return x,y except ValueError as v: print(v) return 0,0 finally: print("finally выполняется до return") x,y = getValues() print(x,y)
Мы создаем функцию для ввода двух целых чисел и в блоке finally выводим сообщение. Этим сообщением мы покажем, что этот блок будет выполняться до операторов return, присутствующих в функции.
Наконец, мы можем прописывать except без указания имени класса исключения:
x = input("x: ") y = input("y: ") try: x = int(x) y = int(y) res = x/y except: print("Произошло исключение") else: print("Исключений не произошло") finally: print("Блок finally выполняется всегда") print(res)
В этом случае оператор print(“Произошло исключение”) будет выполняться при любых ошибках в блоке try, но само исключение обработано не будет, т.е. программа завершится аварийно.
Вот так работает базовый механизм обработки исключений в Python. Конечно, мы здесь рассмотрели только отслеживание стандартных (встроенных) классов исключений. В действительности, можно добавлять свои и строить довольно гибкие программы по обработке исключительных ситуаций. Но для углубления в этот материал, нужно вначале изучить основы ООП в Python. Поэтому мы пока на этом остановимся, и в качестве заданий для самоподготовки попробуйте сделать следующие программы:
Ещё информация
Синтаксис конструкции try и except
Для начала разберем синтаксис операторов try и except в Python. Общий шаблон представлен ниже:
try: # В этом блоке могут быть ошибки except <error type>: # Сделай это для обработки исключения; # выполняется, если блок try выбрасывает ошибку else: # Сделай это, если блок try выполняется успешно, без ошибок finally: # Этот блок выполняется всегда
Давайте посмотрим, для чего используются разные блоки.
Блок try
Блок try
— это блок кода, который вы хотите попробовать выполнить. Однако во время выполнения из-за какого-нибудь исключения могут возникнуть ошибки. Поэтому этот блок может не работать должным образом.
Блок except
Блок except
запускается, когда блок try
не срабатывает из-за исключения. Инструкции в этом блоке часто дают некоторый контекст того, что пошло не так внутри блока try
.
Если собираетесь перехватить ошибку как исключение, в блоке except
нужно обязательно указать тип этой ошибки. В приведенном выше сниппете место для указания типа ошибки обозначено плейсхолдером <error type>
.
except
можно использовать и без указания типа ошибки. Но лучше так не делать. При таком подходе не учитывается, что возникающие ошибки могут быть разных типов. То есть вы будете знать, что что-то пошло не так, но что именно произошло, какая была ошибка — вам будет не известно.
При попытке выполнить код внутри блока try
также существует вероятность возникновения нескольких ошибок.
Например, вы можете попытаться обратиться к элементу списка по индексу, выходящему за пределы допустимого диапазона, использовать неправильный ключ словаря и попробовать открыть несуществующий файл – и все это внутри одного блока try
.
В результате вы можете столкнуться с IndexError
, KeyError
и FileNotFoundError
. В таком случае нужно добавить столько блоков except
, сколько ошибок ожидается – по одному для каждого типа ошибки.
Блок else
Блок else
запускается только в том случае, если блок try
выполняется без ошибок. Это может быть полезно, когда нужно выполнить ещё какие-то действия после успешного выполнения блока try
. Например, после успешного открытия файла вы можете прочитать его содержимое.
Блок finally
Блок finally
выполняется всегда, независимо от того, что происходит в других блоках. Это полезно, когда вы хотите освободить ресурсы после выполнения определенного блока кода.
Примечание: блоки else
и finally
не являются обязательными. В большинстве случаев вы можете использовать только блок try
, чтобы что-то сделать, и перехватывать ошибки как исключения внутри блока except
.
Рассмотрим на примере.
try: for i in range(3): print(3/i) except: print("Деление на 0") print("Исключение было обработано")
Программа вывела сообщение, потому что было обработано исключение.
Следом идет блок except
. Если не определить тип исключения, то он будет перехватывать любые. Другими словами, это общий обработчик исключений.
Если код в блоке try
приводит к исключению, интерпретатор ищет блок except
, который указан следом. Оставшаяся часть кода в try
исполнена не будет.
Исключения Python особенно полезны, если программа работает с вводом пользователя, ведь никогда нельзя знать, что он может ввести.
Несколько except в Python
У одного блока try
может быть несколько блоков except
. Рассмотрим примеры с несколькими вариантами обработки.
a, b = 1, 0 try: print(a/b) print("Это не будет напечатано") print('10'+10) except TypeError: print("Вы сложили значения несовместимых типов") except ZeroDivisionError: print("Деление на 0")
Когда интерпретатор обнаруживает исключение, он проверяет блоки except
соответствующего блока try
. В них может быть объявлено, какие типы исключений они обрабатывают. Если интерпретатор находит соответствующее исключение, он исполняет этот блок except
.
В первом примере первая инструкция приводит к ZeroDivisionError
. Эта ошибка обрабатывается в блоке except
, но инструкции в try
после первой не исполняются. Так происходит из-за того, что после первого исключения дальнейшие инструкции просто пропускаются. И если подходящий или общий блоки except
не удается найти, исключение не обрабатывается. В таком случае оставшаяся часть программы не будет запущена.
Но если обработать исключение, то код после блоков except
и finally
исполнится. Попробуем.
a, b = 1, 0 try: print(a/b) except: print("Вы не можете разделить на 0") print("Будет ли это напечатано?")
Рассмотрим вывод:
Вы не можете разделить на 0
Будет ли это напечатано?
Несколько исключений в одном except
Можно использовать один блок except
для обработки нескольких исключений. Для этого используются скобки. Без них интерпретатор вернет синтаксическую ошибку.
try: print('10'+10) print(1/0) except (TypeError,ZeroDivisionError): print("Неверный ввод")
Неверный ввод
Общий except после всех блоков except
В конце концов, завершить все отдельные блоки except
можно одним общим.
Он используется для обработки всех исключений, которые не были перехвачены отдельными except
.
try: print('1'+1) print(sum) print(1/0) except NameError: print("sum не существует") except ZeroDivisionError: print("Вы не можете разделить на 0") except: print("Что-то пошло не так...")
Здесь первая инструкция блока пытается осуществить операцию конкатенации строки python с числом. Это приводит к ошибке TypeError
. Как только интерпретатор сталкивается с этой проблемой, он проверяет соответствующий блок except
, который ее обработает.
Отдельную инструкцию нельзя разместить между блоками try
и except
.
try: print("1") print("2") except: print("3")
Это приведет к синтаксической ошибке.
Но может быть только один общий или блок по умолчанию типа except
.
Следующий код вызовет ошибку «default 'except:' must be last»
:
try: print(1/0) except: raise except: print("Исключение поймано") finally: print("Хорошо") print("Пока")
Блок finally в Python
После последнего блока except
можно добавить блок finally
. Он исполняет инструкции при любых условиях.
try: print(1/0) except ValueError: print("Это ошибка значения") finally: print("Это будет напечатано в любом случае.")
Это будет напечатано в любом случае. Traceback (most recent call last): File “”, line 2, in print(1/0) ZeroDivisionError: division by zero
Стоит обратить внимание, что сообщение с ошибкой выводится после исполнения блока finally
.
Почему же тогда просто не использовать print
? Но как видно по последнему примеру, блок finally
запускается даже в том случае, если перехватить исключение не удается.
А что будет, если исключение перехватывается в except
?
try: print(1/0) except ZeroDivisionError: print(2/0) finally: print("Ничего не происходит")
Ничего не происходит Traceback (most recent call last): File “”, line 2, in print(1/0) ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File “”, line 4, in print(2/0) ZeroDivisionError: division by zero
Как видите, код в блоке finally
исполняется в любом случае.
Ключевое слово raise в Python
Иногда нужно будет разбираться с проблемами с помощью вызова исключения. Обычная инструкция print
тут не сработает.
raise ZeroDivisionError
Traceback (most recent call last):
File "", line 1, in
raise ZeroDivisionError
ZeroDivisionError
Разберемся на примере операции деления:
a,b=int(input()),int(input()) # вводим 1 затем 0
if b==0:
raise ZeroDivisionError
Traceback (most recent call last):
File "", line 3, in
raise ZeroDivisionError
ZeroDivisionError
Здесь ввод пользователя в переменные a
и b
конвертируется в целые числа. Затем проверяется, равна ли b
нулю. Если да, то вызывается ZeroDivisionError
.
Что будет, если то же самое добавить в блоки try-except? Добавим следующее в код. Если запустить его, ввести 1 и 0, будет следующий вывод:
a,b=int(input()),int(input())
try:
if b==0:
raise ZeroDivisionError
except:
print("Деление на 0")
print("Будет ли это напечатано?")
1
0
Деление на 0
Будет ли это напечатано?
Рассмотрим еще несколько примеров, прежде чем двигаться дальше:
raise KeyError
Traceback (most recent call last):
File “”, line 1, in
raise KeyError
KeyError
Raise без определенного исключения в Python
Можно использовать ключевое слово raise
и не указывая, какое исключение вызвать. Оно вызовет исключение, которое произошло. Поэтому его можно использовать только в блоке except
.
try:
print('1'+1)
except:
raise
Traceback (most recent call last):
File “”, line 2, in
print(‘1’+1)
TypeError: must be str, not int
Raise с аргументом в Python
Также можно указать аргумент к определенному исключению в raise
. Делается это с помощью дополнительных деталей исключения.
raise ValueError("Несоответствующее значение")
Traceback (most recent call last):
File "", line 1, in
raise ValueError("Несоответствующее значение")
ValueError: Несоответствующее значение
assert в Python
Утверждение (assert) — это санитарная проверка для вашего циничного, параноидального «Я». Оно принимает инструкцию в качестве аргумента и вызывает исключение Python, если возвращается значение False
. В противном случае выполняет операцию No-operation (NOP).
assert(True)
# код работает дальше
Если бы инструкция была False
?
assert(1==0)
Traceback (most recent call last):
File “”, line 1, in
assert(1==0)
AssertionError
Возьмем другой пример:
try:
print(1)
assert 2+2==4
print(2)
assert 1+2==4
print(3)
except:
print("assert False.")
raise
finally:
print("Хорошо")
print("Пока")
Вывод следующий:
1
2
assert False.
Хорошо
Traceback (most recent call last):
File “”, line 5, in
assert 1+2==4
AssertionError
Утверждения можно использовать для проверки валидности ввода и вывода в функции.
Второй аргумент для assert
Можно предоставить второй аргумент, чтобы дать дополнительную информацию о проблеме.
assert False,"Это проблема"
Traceback (most recent call last):
File “”, line 1, in
assert False,”Это проблема”
AssertionError: Это проблема
Объявление собственных исключений Python
Наконец, рассмотрим процесс создания собственных исключений. Для этого создадим новый класс из класса Exception
. Потом его можно будет вызывать как любой другой тип исключения.
class MyError(Exception):
print("Это проблема")
raise MyError("ошибка MyError")
Traceback (most recent call last):
File “”, line 1, in
raise MyError(“ошибка MyError”)
MyError: ошибка MyError
Вот и все, что касается обработки исключений в Python.
Полезная ссылка