Перейти к содержимому
Теория · Исключения · урок 2 из 11

try и except

В прошлом уроке весть о трещине поднималась по пунктиру мимо всех ящиков и останавливала программу. Теперь натянем на её пути сетку. Ящик с сеткой ловит весть о поломке, и столбик продолжает работу.

Такая сетка в Python — конструкция try/except. Программа ниже складывает числа из строк. Без try она остановилась бы на первой же строке, непохожей на число:

Python
texts = ["12", "ten", "7"]
total = 0
for text in texts:
    try:
        total += int(text)
    except ValueError:
        print("skip", text)  # skip ten
print(total)  # 19

Давайте разберём по шагам:

  1. Для "12" вызов int проходит, и total становится 12.
  2. Для "ten" вызов int возбуждает ValueError, и except ValueError печатает skip ten.
  3. Для "7" цикл продолжается как обычно, и total становится 19.
  4. Последняя строка печатает 19: ошибка в одной строке не остановила программу.

Как работает try

Python выполняет блок try так:

  1. Строки блока try выполняются по порядку.
  2. Если исключения не было, блок except пропускается.
  3. Если в строке возникло исключение, остаток блока try не выполняется.
  4. Python проверяет, подходит ли исключение к except, и если да — выполняет его блок.

Подходит указанный класс и любой его потомок: except ValueError ловит ValueError, но не KeyError, а except Exception — любую ошибку программы. Такой перехват называют обработкой исключения.

Если except не подошёл, исключение идёт дальше, как будто try не было, и программа останавливается с трассировкой. Поэтому int("ten") под except KeyError всё равно остановит программу. Здесь аналогия с сеткой работает точно: сетка ловит только вести своего вида, а остальные пролетают сквозь неё.

После as в строке except можно дать исключению имя. Под этим именем доступен сам объект исключения, а print показывает его сообщение:

Python
try:
    print(int("ten"))
except ValueError as e:
    print("error:", e)  # error: invalid literal for int() with base 10: 'ten'

Исключение идёт вверх по вызовам

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

Каждая функция на пути бросается посреди строки. Код после упавшей строки в ней не выполняется, и значения она не возвращает. Вернёмся к average и report из прошлого урока, но обернём вызов в try.

Нажмите «Следующий шаг» и следите за столбиком рамок справа. Когда случается ошибка, рамки снимаются одна за другой, а print после упавших строк так и не срабатывают.

ВживуюИсключение поднимается по вызовамшаг 1 из 9 · рамок: 1

Код модуля входит в блок try и вызывает report([]).

Следите, какие строки после ошибки не выполнились

Итак, программа напечатала только три строки. Строки average: … и all done не появились: обе стояли после строк, где летело исключение. Если бы try не нашлось и в <module>, программа остановилась бы с трассировкой из прошлого урока.

Несколько except

У одного try может быть несколько веток except. Python проверяет их сверху вниз и выполняет только первую подходящую, остальные пропускает:

Python
prices = {"tea": "40", "cake": "abc"}
for name in ["tea", "milk", "cake"]:
    try:
        print(int(prices[name]))  # 40
    except KeyError:
        print("no", name)  # no milk
    except ValueError:
        print("bad price", name)  # bad price cake

Разберём каждый товар:

  1. Для tea обе операции в try проходят без ошибки, и программа печатает 40.
  2. Для milk ошибка возникает уже на prices[name]: ключа нет, срабатывает except KeyError. До int дело не доходит.
  3. Для cake ключ есть, но int("abc") возбуждает ValueError. Первая ветка не подходит, и срабатывает вторая.

В Python принято сначала пробовать действие, а неудачу перехватывать. Этот подход называют EAFP — это сокращение английской фразы «проще попросить прощения, чем разрешения». Строку, которую прочитает int, заранее проверить трудно: "-5" и " 7" — числа, а "5.0" — нет. Попытка с except ValueError решает это одной строкой.

На собеседовании про EAFP спрашивают часто. Противоположный подход — сначала проверить, можно ли действовать, и только потом действовать. В Python для разбора строк и поиска ключей обычно выбирают EAFP.

Частая ошибка

Общий класс поставили выше частного:

Python
stock = {"tea": 3}
try:
    print(stock["milk"])
except Exception:
    print("something went wrong")  # something went wrong
except KeyError:  # ✗
    print("no such item")

Python не сообщает об ошибке, но ветка except KeyError не выполнится никогда. KeyError — потомок Exception, и верхняя ветка ловит его первой. Частные классы пишут выше общих: поменяйте ветки местами, и программа напечатает no such item.

Главное

  • try/except перехватывает исключение, и программа продолжает работу после блока except.
  • После упавшей строки остаток блока try не выполняется; except ловит указанный класс и его потомков.
  • Исключение поднимается по вызовам, бросая каждую функцию посреди строки, пока его не поймает подходящий except.
  • Ветки except проверяются сверху вниз, поэтому частные классы пишут выше общих.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом