Ошибки и как их перехватывать

Иногда, во время работы программы случаются непредвиденные ситуации. Например, попытка поделить число на ноль или чтение несуществующего файла вызовут ошибку и остановят выполнение программы. Такие ситуации в программировании называют исключительными ситуациями, а ошибки - исключениями (exception). Для разных ситуаций есть свои типы исключений:

2 / 0  # ZeroDivisionError: division by zero

open('non_existing_file.txt')  # FileNotFoundError: No such file or directory

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

# Файл example.py

def average(numbers):
    return sum(numbers) / len(numbers)

average([1, 2, 3])  # Вернёт 2
average([])  # Сделает ба-бах!

Выйдет такая ошибка:

Traceback (most recent call last):        # 6
  File "example.py", line 5, in <module>  # 5
    average([])                           # 4
  File "example.py", line 2, in average   # 3
    return sum(numbers) / len(numbers)    # 2
ZeroDivisionError: division by zero       # 1

Простыня текста на последних шести строчках называется трэйсбэком. Он выглядит страшно, но читать его — полезно. В нём Python рассказывает что же случилось.

Важно: трейсбек читают снизу вверх. Именно поэтому мы расставили такую нумерацию строчек в примере.

Давайте разберём каждую строчку в примере выше:

1) ZeroDivisionError: division by zero — тип исключения и сообщение с причиной ошибки. Если перевести на русский, сразу понятно: ОшибкаДеленияНаНоль: вы поделили на ноль.

2) return sum(numbers) / len(numbers) — строка, на которой произошла ошибка.

3) File "example.py", line 2, in average — где эту строку искать. В файле example.py, на второй строке, в функции average.

4 и 5) Начиная с этой строки и далее, Python будет указывать какие строки кода исполнялись до момента, с которого началась вся цепочка. Эта информация нужна, когда ошибка произошла где-то внутри чужого кода. Просматривая трейсбэк снизу вверх, можно найти строчку вашего кода, которая стала причиной ошибки.

6) Начало трейсбэка

Каким бы длинным трейсбек ни был, в большинстве случаев вам понадобится только последние две-три строки. Как правило, их достаточно, чтобы определить что и где произошло. Выходит, трейсбэки не такие уж и страшные, если знать, где искать.

Как перехватить ошибку

Если возникшее исключение — часть плана или вы хотите обработать его особенном образом, то на такой случай в Python существует конструкция try-except:

filepath = user.get_avatar_filepath()
try:
    avatar = Image.open(filepath)
except FileNotFoundError:
    avatar = default_avatar

Внутри блока try(внутри — это с отступами) пишется код, который потенциально может вызвать ошибку. Если исключения не произойдёт, то Python проигнорирует блок except и пойдёт дальше. Если же возникла ошибка — сработает код внутри блока except.

Обратите внимание, что после except стоит тип исключения, который может случиться внутри try. Это правило хорошего тона. Мы явно указываем тип ошибки, которую ожидаем.

Код, в котором не указан тип ошибки выглядит так:

filepath = user.get_avatar_filepath()
try:
    avatar = use_avatar(filepath)
except:
    avatar = default_avatar

Однажды может случится страшное: другая ошибка. Но код будет себя вести так, будто ничего страшного не произошло, будто он просто не нашёл файл. Python не покажет трейсбек и чинить код придется методом ненаучного тыка.

Когда разработчик не указывает тип исключения, кажется, будто он просто написал такой плохой код, что сам себе не доверяет. Без особой необходимости так делать нельзя.