Обработка исключений в Python с использованием инструкций try, except и finally

Из данной статьи вы узнаете, как обрабатывать исключения в программах, написанных на языке Python, при помощи инструкций try, except и finally. Также все это будет проиллюстрированно многочисленными примерами.

Исключения в Python

В языке Python существует множество встроенных исключений, которые вызываются, когда программа сталкивается с ошибкой (что-то в программе идет не так).

Когда такое исключение возникает, интерпретатор Python останавливает текущий процесс и передает управление новому, для обработки данного исключения. Если его не удается обработать, программа аварийно завершается.

Например, пусть в нашей программе есть функция А, которая вызывает функцию В, которая в свою очередь вызывает функцию С. Если исключение возникнет в функции C, но не будет в этой функции обработано, то оно будет передано в функцию B и далее в функцию А.

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

Перехват исключений в Python

В Python исключения обрабатываются при помощи инструкции try.

Критическая операция, которая может вызвать исключение, помещается внутрь блока try. А код, при помощи которого это исключение будет обработано, — внутрь блока except.

Таким образом, мы можем выбрать набор операций, который мы хотим совершить при перехвате исключения. Вот простой пример.

# Для получения типа исключения импортируем модуль sys
import sys

randomList = ['a', 0, 2]

for entry in randomList:
    try:
        print("The entry is", entry)
        r = 1/int(entry)
        break
    except:
        print("Oops!", sys.exc_info()[0], "occurred.")
        print("Next entry.")
        print()
print("The reciprocal of", entry, "is", r)

Результат:

The entry is a
Oops! <class 'ValueError'> occurred.
Next entry.

The entry is 0
Oops! <class 'ZeroDivisionError'> occured.
Next entry.

The entry is 2
The reciprocal of 2 is 0.5

В данном примере мы, при помощи цикла for, производим итерацию списка randomList. Как мы ранее заметили, часть кода, в которой может произойти ошибка, помещена внутри блока try.

Если исключений не возникает, блок except пропускается, а программа продолжает выполнятся обычным образом (в данном примере так происходит с последним элементом списка). Но если исключение появляется, оно сразу обрабатывается в блоке except (в данном примере так происходит с первым и вторым элементом списка).

В нашем примере мы вывели на экран имя исключения при помощи функции exc_info(), которую импортировали из модуля sys. Можно заметить, что элемент a вызывает ValueError, а 0 вызывает ZeroDivisionError.

Так как все исключения в Python наследуются из базового класса Exception, мы можем переписать наш код следующим образом:

# Для получения типа исключения импортируем модуль sys
import sys

randomList = ['a', 0, 2]

for entry in randomList:
    try:
        print("The entry is", entry)
        r = 1/int(entry)
        break
    except Exception as e:
        print("Oops!", e.__class__, "occurred.")
        print("Next entry.")
        print()
print("The reciprocal of", entry, "is", r)

Результат выполнения этого кода будет точно таким же.

Перехват определенных исключений в Python

В предыдущем примере мы не специфицировали перехватываемые исключения в блоке except.

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

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

Чтобы определить несколько исключений для одного блока except, мы можем использовать кортеж из значений исключений. Вот примерный псевдокод:

try:
   # do something
   pass

except ValueError:
   # handle ValueError exception
   pass

except (TypeError, ZeroDivisionError):
   # handle multiple exceptions
   # TypeError and ZeroDivisionError
   pass

except:
   # handle all other exceptions
   pass

Вызов исключений в Python

В Python исключения автоматически вызываются, когда во время выполнения программы возникает ошибка. Но мы и сами можем вызвать исключение, используя для этого инструкцию raise.

При желании мы можем передать значения в исключение, чтобы выяснить, почему вообще возникло это исключение.

>>> raise KeyboardInterrupt
Traceback (most recent call last):
...
KeyboardInterrupt

>>> raise MemoryError("This is an argument")
Traceback (most recent call last):
...
MemoryError: This is an argument

>>> try:
...     a = int(input("Enter a positive integer: "))
...     if a <= 0:
...         raise ValueError("That is not a positive number!")
... except ValueError as ve:
...     print(ve)
...    
Enter a positive integer: -2
That is not a positive number!

Конструкция try с блоком else

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

Замечание: исключения, возникающие в самом блоке else, не будут обработаны в предшествующем ему блоке except.

Давайте рассмотрим следующий пример:

# программа для вывода на экран обратных значений четных чисел

try:
    num = int(input("Введите число: "))
    assert num % 2 == 0
except:
    print("Число нечетное!")
else:
    reciprocal = 1/num
    print(reciprocal)

Результат:

Если мы вводим нечетное число:

Введите число: 1
Число нечетное!

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

Введите число: 4
0.25

Однако, если мы введем 0, то получим в результате ошибку ZeroDivisionError, так как код в блоке else не обрабатывается в предшествующем ему блоке except.

Введите число: 0
Traceback (most recent call last):
  File "<string>", line 7, in <module>
    reciprocal = 1/num
ZeroDivisionError: division by zero

Конструкция try…finally

Инструкция try может также иметь и необязательный блок finally. Этот блок кода будет выполнен в любом случае и обычно используется для освобождения внешних ресурсов.

Например, мы можем быть подключены через сеть к удаленному дата-центру, или работать с файлом или с GUI (Graphical User Interface — графический интерфейс пользователя).

Во всех таких случаях мы должны закрыть эти ресурсы вне зависимости от того, успешно завершилась наша программа или нет. Все эти действия (закрытие файла или GUI, отключение от сети) можно выполнять в блоке finally, чтобы гарантировать их исполнение.

Вот пример операций с файлом, который иллюстрирует это:

try:
   f = open("test.txt",encoding = 'utf-8')
   # perform file operations
finally:
   f.close()

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