Где обработать исключение

Исключения в Python — это особый объект, который умеет путешествовать между функциями и менять их поведение. Своим появлением исключение прерывает обычное исполнение программы — сверху вниз и вглубь — и переводит его в особый обратный режим — наверх до подходящего try except finally.

Для примера рассмотрим программу — информер с прогнозом погоды. Функция request_weather делает запрос к API сайта weather.com и возвращает прогноз погоды:

def request_weather():
    response = requests.get('https://weather.com/api/weather/moscow/')
    return response.json()

Если с сервером weather.com не удаётся связаться, то вызов метода requests.get приводит к исключению ConnectionError — к ошибке соединения:

def request_weather():
    response = requests.get('https://weather.com/api/weather/moscow/')
    return response.json()

request_weather()
# requests.exceptions.ConnectionError:
# ...
# Failed to establish a new connection: [Errno -2] Name or service not known',))

По логике программы при сбое на одном сайте мы тянем информацию с запасного сайта openweathermap.org:

def request_weather():
    response = requests.get('https://weather.com/api/weather/moscow/')
    return response.json()

def request_openweathermap():
    response = requests.get('https://openweathermap.org/api/moscow/')
    return response.json()

request_weather()
# TODO в случае сбоя вызывать `request_openweathermap`

Здесь начинаются сложности. Нельзя перехватывать исключение ConnectionError внутри функции request_weather, иначе внешний код не узнает о проблеме и не сможет переключиться на вызов второй функции request_openweathermap. Перехватывать исключение нужно снаружи:

def request_weather():
    response = requests.get('https://weather.com/api/weather/moscow/')
    return response.json()

def request_openweathermap():
    response = requests.get('https://openweathermap.org/api/moscow/')
    return response.json()

try:
    forecast = request_weather()
except requests.exceptions.ConnectionError:
    forecast = request_openweathermap()

Исключение возникло внутри метода requests.get и всплыло вверх — внутрь функции def request_weather. Там не нашлось подходящего обработчика исключений, потому что не было конструкций try except и поэтому исключение продолжило всплывать выше — наружу функции request_weather. Подходящий обработчик нашелся в последних строках кода:

try:
    forecast = request_weather()
except requests.exceptions.ConnectionError:
    forecast = request_openweathermap()

Исключение всегда всплывает вверх по цепочке вызовов функций, пока его не перехватят. Если ни одна функция так и не обработает исключение, то вмешается сам интерпретатор Python: он перехватывает все что до него долетает и выводит на экран:

def request_weather():
    response = requests.get('https://weather.com/api/weather/moscow/')
    response.raise_for_status()  # здесь возникло исключение
    return response.json()

def request_openweathermap():
    response = requests.get('https://openweathermap.org/api/moscow/')
    response.raise_for_status()
    return response.json()

def main():
    try:
        forecast = request_weather()
    except requests.exceptions.ConnectionError:
        forecast = request_openweathermap()

main()  # здесь исключение будет перехвачено Питоном
# requests.exceptions.HTTPError: 404 Client Error: Not Found for url ...
# ...

В примере выше исключение HTTPError возникло внутри метода response.raise_for_status(), затем всплыло в функцию def request_weather(): и там его никто не обработал. Тогда исключение всплыло еще выше в функцию def main():, но и там обработчика не нашлось. Исключение всплыло еще выше и оказалось на уровне интерпретатора Python — он перехватил исключение и вывел его на экран.

Где ловить исключения

Основная идея: исключение ловят не там, где оно возникло, а там где его удобно обработать. Исключение само будет всплывать от функции к функции пока не встретит подходящий обработчик, делать для этого ничего не надо, достаточно не мешать.

Алгоритм действий следующий. Сначала выкидываем исключение, чтобы сообщить об ошибке и прервать нормальное исполнение программы. Затем ищем место в коде, где это исключение удобнее всего обработать, и сразу придумываем логику обработки такого исключения. Если места не нашлось или обработка сводится к print(error), то отказываемся от перехвата исключения — интерпретатор Python в любом случае перехватывает все исключения и выводит их на экран.


Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.