Хотя, чаще всего, мы будем сталкиваться с исключениями в контексте работы над ошибками в коде, будет не совсем правильным ставить знак равенства между ними. Исключения скорее события, которые способны изменить поток управления в программе.
Что это означает? Когда наша программа по ходу своего выполнения сталкивается с исключением, она:
Либо аварийно завершается стандартным обработчиком исключений. И мы видим в консоли вывод, подобный этому:
Traceback (most recent call last):
File "/home/.../test.py", line 4, in <module>
print(get_letter('spam', 4))
^^^^^^^^^^^^^^^^^^^^^
File "/home/.../test.py", line 2, in get_letter
return word[index]
~~~~^^^^^^^
**IndexError**: string index out of range
Process finished with exit code 1
Либо, в случае если мы предусмотрели обработку исключения, поток управления будет перенаправлен в блок с этой обработкой, где мы сможем произвести нужные нам операции. Например:
while True:
try:
x = int(input('Введи целое число: = '))
except **ValueError**:
print('Неверно! Попробуй ещё раз')
else:
print(f'Отлично! Твоё число - {x}')
break
# Вывод:
Введи целое число: = Hi!
Неверно! Попробуй ещё раз
Введи целое число: = 14/1
Неверно! Попробуй ещё раз
Введи целое число: = 7
Отлично! Твоё число - 7
Обработка ошибок
Это, пожалуй, самая очевидная роль исключений. Когда интерпретатор Python обнаруживает ошибку во время выполнения программы, он генерирует исключение. У нас есть выбор: либо перехватить это исключение и как-то на него отреагировать, либо позволить Python сделать это за нас (что обычно приводит к остановке программы и выводу сообщения об ошибке). Если нам нужно что-то более изящное, чем просто "упасть с ошибкой", мы используем конструкцию try (как в примере выше) для перехвата и обработки исключения.
Уведомление о событиях
Исключения - это не только про ошибки. Мы можем использовать их для сообщения о каких-то особых ситуациях в программе. Например, вместо того чтобы возвращать специальное значение из функции поиска (скажем, -1, если элемент не найден), мы можем генерировать исключение. Это избавляет нас от необходимости постоянно проверять возвращаемое значение и делает код более чистым и понятным.
Упрощение логики программы
Добавление в код проверок на все случаи жизни может сильно усложнить его и сделать трудночитаемым. Вместо этого мы можем использовать исключения. Основной код остается чистым и фокусируется на "счастливом пути" - обработке корректных данных. А все нестандартные ситуации обрабатываются в блоках except. Это не только упрощает чтение и понимание кода, но и делает его более гибким.
Действия при завершении
Исключения могут выполнять роль "уборщиков", приводя всё в порядок в конце работы программы, независимо от того, завершилась она успешно или нет. С помощью блока finally
(будет описан подробно далее) можно гарантировать, что такие ресурсы, как файлы или сетевые соединения, будут закрыты, даже если произошла ошибка. Это помогает предотвратить утечки памяти и другие проблемы, которые могут возникнуть при неправильном управлении ресурсами.
Как мы уже видели ранее, в Python предусмотрено возникновение ситуаций, в которых программа не может быть выполнена корректно. И при нашей встрече с такими ситуациями, интерпретатор будет вызывать исключения за нас.
Например, если мы попытаемся получить значение из словаря по ключу, которого в этом словаре просто нет, мы получим исключение - KeyError.
def get_value(key):
some_value = {
1: 'one',
2: 'two',
3: 'three',
}
return some_value[key]
print(get_value(4))
print('Программа завершена')
# Вывод:
Traceback (most recent call last):
File "/home/.../test.py", line 9, in <module>
print(get_value(4))
^^^^^^^^^^^^
File "/home/.../test.py", line 7, in get_value
return some_value[key]
~~~~~~~~~~^^^^^
**KeyError**: 4
Process finished with exit code 1
И что важно — программа завершает выполнение сразу же, как только было выброшено исключение. Мы не видим в выводе - 'Программа завершена'
.
Способ, который возвращает инициативу в наши руки и позволяет уже нам самим контролировать вызов исключения, является инструкция raise.