try и except
В прошлом уроке весть о трещине поднималась по пунктиру мимо всех ящиков и останавливала программу. Теперь натянем на её пути сетку. Ящик с сеткой ловит весть о поломке, и столбик продолжает работу.
Такая сетка в Python — конструкция try/except. Программа ниже складывает числа из строк. Без try она остановилась бы на первой же строке, непохожей на число:
texts = ["12", "ten", "7"]
total = 0
for text in texts:
try:
total += int(text)
except ValueError:
print("skip", text) # skip ten
print(total) # 19Давайте разберём по шагам:
- Для
"12"вызовintпроходит, иtotalстановится12. - Для
"ten"вызовintвозбуждаетValueError, иexcept ValueErrorпечатаетskip ten. - Для
"7"цикл продолжается как обычно, иtotalстановится19. - Последняя строка печатает
19: ошибка в одной строке не остановила программу.
Как работает try
Python выполняет блок try так:
- Строки блока
tryвыполняются по порядку. - Если исключения не было, блок
exceptпропускается. - Если в строке возникло исключение, остаток блока
tryне выполняется. - Python проверяет, подходит ли исключение к
except, и если да — выполняет его блок.
Подходит указанный класс и любой его потомок: except ValueError ловит ValueError, но не KeyError, а except Exception — любую ошибку программы. Такой перехват называют обработкой исключения.
Если except не подошёл, исключение идёт дальше, как будто try не было, и программа останавливается с трассировкой. Поэтому int("ten") под except KeyError всё равно остановит программу. Здесь аналогия с сеткой работает точно: сетка ловит только вести своего вида, а остальные пролетают сквозь неё.
После as в строке except можно дать исключению имя. Под этим именем доступен сам объект исключения, а print показывает его сообщение:
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 после упавших строк так и не срабатывают.
1def average(nums):2 return sum(nums) / len(nums)3 4 5def report(nums):6 print("report starts")7 avg = average(nums)8 print("average:", avg)9 10 11try:12 report([])13 print("all done")14except ZeroDivisionError as e:15 print("caught:", e)16print("program goes on")
Код модуля входит в блок try и вызывает report([]).
Итак, программа напечатала только три строки. Строки average: … и all done не появились: обе стояли после строк, где летело исключение. Если бы try не нашлось и в <module>, программа остановилась бы с трассировкой из прошлого урока.
Несколько except
У одного try может быть несколько веток except. 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Разберём каждый товар:
- Для
teaобе операции вtryпроходят без ошибки, и программа печатает40. - Для
milkошибка возникает уже наprices[name]: ключа нет, срабатываетexcept KeyError. Доintдело не доходит. - Для
cakeключ есть, ноint("abc")возбуждаетValueError. Первая ветка не подходит, и срабатывает вторая.
В Python принято сначала пробовать действие, а неудачу перехватывать. Этот подход называют EAFP — это сокращение английской фразы «проще попросить прощения, чем разрешения». Строку, которую прочитает int, заранее проверить трудно: "-5" и " 7" — числа, а "5.0" — нет. Попытка с except ValueError решает это одной строкой.
На собеседовании про EAFP спрашивают часто. Противоположный подход — сначала проверить, можно ли действовать, и только потом действовать. В Python для разбора строк и поиска ключей обычно выбирают EAFP.
Частая ошибка
Общий класс поставили выше частного:
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 и подпишись на канал — откроются все уроки.