Исключения и обработка ошибок
где ошибка возникает и где её ловят
Что это
Исключение — способ сообщить, что дальше работать нельзя. Строка raise
поднимает ошибку, блок try/except её ловит. Между ними выполнение
перепрыгивает через весь код, который был между.
Читать этот механизм надо по двум вопросам: где ошибка возникает и где её ловят. Между ними может быть десяток функций — именно это и показывает трейсбек (статья 2.12).
Как выглядит в коде
def parse_port(raw: str) -> int:
try:
port = int(raw)
except ValueError:
raise ValueError(f"порт должен быть числом, получено: {raw!r}") from None
if not 1 <= port <= 65535:
raise ValueError(f"порт вне диапазона: {port}")
return port
for raw in ["8080", "abc", "99999"]:
try:
print(raw, "->", parse_port(raw))
except ValueError as e:
print(raw, "-> ошибка:", e)Вывод:
8080 -> 8080
abc -> ошибка: порт должен быть числом, получено: 'abc'
99999 -> ошибка: порт вне диапазона: 99999Как читать
try: — «попробуй выполнить». Если внутри что-то упало, выполнение
немедленно переходит в except, остаток try пропускается.
except ValueError: — ловит только ошибки этого вида. Другие пролетят
мимо и уйдут выше.
raise — поднять ошибку. Здесь функция превращает невнятное
invalid literal for int() в понятное сообщение со значением, которое не
подошло.
as e — поместить объект ошибки в переменную, чтобы прочитать текст.
finally: — выполнится в любом случае, с ошибкой или без. Нужен для
закрытия файлов и соединений.
Иерархия ошибок
Ошибки образуют дерево: ValueError и KeyError — частные случаи
Exception. Поэтому except Exception ловит почти всё, а except ValueError
— только своё.
| Вид | Когда бывает |
|---|---|
ValueError | значение не того вида: int("abc") |
KeyError | нет такого ключа в словаре |
TypeError | не тот тип: сложили строку с числом |
AttributeError | нет такого поля или метода (часто у None) |
FileNotFoundError | файла нет |
TimeoutError | не дождались ответа |
Эти шесть покрывают большинство случаев, которые встретятся при чтении.
Ловить надо там, где знаешь, что делать
Правило, по которому проверяют расстановку try: ошибку ловят там, где есть
осмысленный ответ на вопрос «а что теперь?».
В примере parse_port не ловит собственную ошибку диапазона — она
не знает, что делать с неверным портом. А вызывающий код знает: напечатать
сообщение и перейти к следующему значению.
Поэтому обёртка try вокруг каждой строки — признак, что автор не думал о
том, кто и как будет на ошибку реагировать.
Как сказать ИИ
Какие исключения может поднять эта функция и где они ловятся?
Если нигде — так и скажи.Здесь стоит except Exception. Какие конкретно ошибки
ты ожидал поймать? Сузь до них.Второй запрос — рабочий способ чинить слишком широкие except: ИИ обычно
может назвать конкретные виды, если спросить.
На что смотреть
except Exception: pass — ошибка проглочена целиком. Код не падает и
поэтому кажется рабочим.
except без типа. Голое except: ловит даже прерывание по Ctrl+C.
Ошибка поймана, но не записана. Если в except нет ни log, ни
повторного raise, информация потеряна навсегда.
Сообщение без данных. raise ValueError("неверное значение") — какое
именно? Хорошее сообщение всегда содержит то, что не подошло.
try вокруг двадцати строк. Непонятно, какая из них может упасть.
Сужение try до одной-двух строк — самый дешёвый способ сделать код
читаемым.
Шпаргалка
raiseподнимает,try/exceptловит, между ними выполнение прыгает.except ValueErrorловит только своё,except Exception— почти всё.Шесть частых ошибок: Value · Key · Type · Attribute · FileNotFound · Timeout.
Лови там, где знаешь, что делать дальше.
except Exception: pass— ошибка проглочена, код врёт.В сообщении об ошибке всегда указывай значение, которое не подошло.