Введение в обработку исключений

Начиная с этого занятия, мы коснемся довольно важной темы в программировании – обработка исключений.  Давайте вначале я поясню, что стоит за этими «умными» словами. Предположим, мы в программе строчка за строчкой выводим некий многострочный текст:

print("Куда ты скачешь, гордый конь,")
print("И где опустишь ты копыта?")
print("О мощный властелин судьбы!")
print("Не так ли ты над самой бездной")
print("На высоте, уздой железной")
print("Россию поднял на дыбы?")

И где-нибудь в серединке, после третьего print(), запишем команду:

print(a)

Разумеется, в момент выполнения этой программы, возникнет ошибка, а точнее – исключение с типом:

NameError

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

10/0

то отобразится тип исключения:

ZeroDivisionError

которое также относится к моменту выполнения программы, так как первые три строчки (до ошибки) были успешно выполнены.

Другой вариант ошибок, это, например, неверный синтаксис в программе. Если, допустим, прописать строчку:

if True
    print("True")

то увидим исключение типа:

SyntaxError

которое возникает в момент компиляции текста программы, поэтому, ни одной строчки не было отображено в консоли. То есть, исключения можно разбить на две группы:

  • исключения в момент исполнения;
  • исключения при компиляции (до исполнения кода).

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

На первый взгляд может показаться, что исключения возникают по вине программиста, из-за того, что он что-то не учел. Иногда такое бывает, но не всегда. Классический пример обработки исключений момента выполнения программы – открытие файлов на чтение или запись. Мы с вами уже об этом говорили, когда открывается файл:

file = open("myfile.txt")

то программист не может заранее знать, есть ли такой файл на диске или нет. Поэтому, такую строчку нужно выполнять в специальном блоке try с обработкой исключения типа FileNotFoundError:

try:
    file = open("myfile2.txt")
except FileNotFoundError:
    print("Невозможно открыть файл")

Я не буду здесь повторяться и рассказывать о порядке работы команд

try/except/finally

Если вы не смотрели это занятие, то вот ссылка на него:

https://www.youtube.com/watch?v=Ccry8wMJ39o&list=PLA0M1Bcd0w8xIdFNA95aQrwJ_GQJEV8ko

Отмечу только, что если в нашей программе строчку:

10/0

Поместить в блок try с обработкой исключения ZeroDivisionError:

try:
    10/0
except ZeroDivisionError:
    print("Делить на ноль нельзя!")

то выполнение программы не остановится, исключение будет обработано, и мы увидим все строчки текста.

Разумеется, так обрабатывать можно только исключения на этапе исполнения, то есть, синтаксические ошибки, например, так не исправляются, нужно правильно записывать команды и конструкции языка Python. Поэтому, мы сейчас говорим именно об исключениях в момент исполнения. И, как мы только что отмечали, у них может быть множество самых разных типов. Так вот, эти типы есть не что иное, как имена классов унаследованные от базового класса BaseException:

Здесь приведен лишь фрагмент иерархии классов исключений. Самих классов гораздо больше. Из этого рисунка хорошо видно, что большая часть классов исключений наследуется от базового класса Exception. Поэтому, мы его будем часто использовать.

На что влияет эта иерархия и как ее можно использовать при обработке исключений? Например, мы видим обработку исключения деления на ноль, которое относится к базовому классу ArithmeticError, а он, в свою очередь, к базовому классу Exception. Поэтому, вместо указания конкретного типа ZeroDivisionError можно указать любой из этих базовых классов:

try:
    10/0
except ArithmeticError:
    print("Делить на ноль нельзя!")

Далее, пусть выполняется вот такое ошибочное сложение:

try:
    1+"f"
except TypeError as e:
    print(e)

Оно вызывает тип исключения TypeError, который является дочерним по отношению к тому же базовому классу Exception. Поэтому, вместо TypeError можно прописать:

try:
    1+"f"
except Exception as e:
    print(e)

И результат будет тем же. Мало того, теперь блок except будет отлавливать любые типы исключений, унаследованные от этого базового класса. Например, то же деление на ноль. Вот так, благодаря ООП и наследованию, мы получаем более гибкий механизм обработки исключений.

Видео по теме