Обработка исключений в Python

При работе с сетевыми ресурсами велика вероятность сбоев: на отправленный запрос не вернулся ответ, упал сервер, отвалился интернет, электричество кончилось, да мало ли что ещё может случиться. Любой из таких сбоев может вызвать ошибку в выполнении программы.

Обработка различного рода ошибок — обязательная часть любого проекта. Ошибки при выполнении кода возникают не только в проектах, связанных с постоянным обменом данными по сети.

Исключение (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') 


Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: