При работе с сетевыми ресурсами велика вероятность сбоев: на отправленный запрос не вернулся ответ, упал сервер, отвалился интернет, электричество кончилось, да мало ли что ещё может случиться. Любой из таких сбоев может вызвать ошибку в выполнении программы.
Обработка различного рода ошибок — обязательная часть любого проекта. Ошибки при выполнении кода возникают не только в проектах, связанных с постоянным обменом данными по сети.
Исключение (Exception) — это тип данных, назначение которого — предупреждать разработчика о нештатных ситуациях при работе программы. Исключения указывают на какую-то ошибку, которую можно исправить, если правильно отреагировать на неё.
Исключения возникнут, например, при попытке прочитать несуществующий файл; при запросе к отключённому серверу; при попытке деления на ноль.
Исключения останавливают работу программы, если разработчик заранее не укажет в коде, как реагировать на них. Такая обработка называется перехват исключений.
Один из простых способов вызвать исключение — разделить на ноль. При этом интерпретатор выведет в консоль traceback — цепочку функций, вызов которых предварял появление ошибки. Обычно трейсбек бывает длинным и иерархическим: в программе могут вызываться разные функции из разных пакетов. Но может быть и таким, лаконичным:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Этот трейсбек показывает, что в первой строке кода произошла ошибка деления на ноль, получено исключение ZeroDivisionError и выполнение программы остановлено.
В Python есть множество встроенных исключений, с некоторыми из них вы уже сталкивались. Вероятно, в топе популярности было исключение SyntaxError, оно возникает при синтаксических ошибках и не требует программной обработки: нужно просто поправить код.
Все исключения наследуются от базового класса Exception, расширяя его. Иерархия исключений приведена в документации.
Хорошей практикой считается создавать свои собственные исключения там, где они необходимы. Они создаются как класс, который наследуется от базового класса Exception либо от какого-то дочернего класса, который подходит к задаче.
Вот пример описания и использования собственного исключения:
class NegativeValueException(Exception):
pass
def set_robot_power(value):
if value < 0:
raise NegativeValueException('Введите число, которое больше нуля!')
set_robot_power(-1)
Программа остановится, а в консоль будет выведено соответствующее сообщение.
Работа над ошибками
Есть два подхода к обработке ошибок:
Look Before You Leap (LBYL) — «посмотри, прежде чем прыгнуть»: заранее предусмотри возможные ошибки;
Easier to Ask for Forgiveness than Permission (EAFP) «проще просить прощения, чем разрешения»: перехвати ошибку, если она произошла.
Заранее предусмотри ошибки
Этот подход заключается в том, чтобы просчитать все возможные ситуации. Известно, что на ноль делить нельзя, значит, перед операцией деления нужно проверить, что делитель не равен нулю:
if divisor != 0:
result = numerator / divisor
else:
result = None
Или, в случае с чтением файла, сперва нужно убедиться, что файл существует, и только затем открывать его.
Перехвати ошибку
Этот подход более универсален и гибок: в блоке кода заранее описывается поведение на тот случай, если что-то пойдёт не так. То есть мы предполагаем, что всё будет работать, но на всякий случай описываем запасной вариант:
try:
result = numerator / divisor
except ZeroDivisionError:
result = None
print("На ноль делить нельзя!")
Синтаксис здесь такой:
try:
# Выполняем этот блок кода
except <определённая разновидность исключений>:
# Выполняем этот блок, если возникло определённое исключение,
# таких блоков может быть много
except <другая разновидность исключений>:
# Выполняем этот блок, если возникло определённое исключение,
# таких блоков может быть много
except:
# Выполняем этот блок, если возникло любое исключение
else:
# Выполняем этот блок, если не возникло исключений (опциональный блок)
finally:
# Этот блок выполнится всегда (опциональный блок)
В блоке try
пишется код, который может вызвать исключение. За ним следует один или несколько блоков except
, в которых описан код, который сработает при определённом исключении. Можно указать тип исключения явно (это хорошая практика), а можно и не указывать — тогда любые исключения будут обрабатываться общим кодом.
«П» — «предусмотрительность»: при обращении к списку можно предположить, что в нём может не оказаться запрошенного элемента, значит, надо перехватить исключение IndexError, а при обращении к словарю стоит ожидать исключения KeyError.
Работа с исключениями в requests
При запросе к API особенно важно проработать исключения: нет никаких гарантий, что запрос пройдёт успешно. Например, провайдер может блокировать доступ к сервису API, или сервис, к которому отправлен запрос, может быть недоступен из-за нагрузки или сбоя, наконец, сам запрос мог быть сформирован неправильно.
Обрабатывать ошибки при совершении запроса можно так:
import requests
try:
response = requests.get('https://ya.ru')
except Exception as error:
# Вывести ошибку
# Попробовать подключиться к другому поисковику
print(error)
# Тут полезный код, например отправка поискового запроса
response = requests.get('https://duckduckgo.com')