Программирование с ИИ
ENRU
2.8

Исключения и обработка ошибок

где ошибка возникает и где её ловят

Что это

Исключение — способ сообщить, что дальше работать нельзя. Строка 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 — ошибка проглочена, код врёт.

  • В сообщении об ошибке всегда указывай значение, которое не подошло.