Исключения в Python: что это такое и как с ними работать
«Сигналы тревоги», которые подаёт программа, когда что-то пошло не так.


Иллюстрация: Freepik / Unsplash / Colowgee для Skillbox Media
Что произойдёт, если в коде на Python встретится ошибка? Как программа отправляет сообщения об ошибках программисту и как написать собственный обработчик? За всё это отвечают исключения. Рассказываем, как они работают и как использовать их в своём коде.
Содержание
Как работают исключения в Python
Исключения (exceptions) в Python — это механизм обработки ошибок во время выполнения программы. Они позволяют программе продолжить работу после обнаружения ошибки, а не завершаться аварийно. В Python есть встроенные исключения, которые обрабатывают большинство типовых ошибок.
Рассмотрим такую ситуацию на примере. В коде ниже вызывается функция f1(), внутри которой находится функция f2(). Выполнение будет продолжаться до тех пор, пока интерпретатор не дойдёт до строчки print (y - 2):
def f2(y):
print(y - 2) # Ошибка
def f1(x):
f2(x)
f1('10')
Из строки нельзя вычесть число, поэтому в консоли появится информация об ошибке в виде трассировки Traceback с исключением TypeError:
Traceback (most recent call last):
File 'c:\Python\except.py', line 10, in <module>
f1('10')
File 'c:\Python\except.py', line 7, in f1
f2(x)
File 'c:\Python\except.py', line 3, in f2
print(y - 2) # Ошибка
~~^~~
TypeError: unsupported operand type(s) for -: 'str' and 'int'
В Python есть встроенные исключения для разных ситуаций. Рассмотрим некоторые из них:
- TypeError — операция или функция применяется к объекту несоответствующего типа.
a = 2
b = 'два'
print(a + b) # TypeError: unsupported operand type(s) for +: 'int' and 'str'
- ValueError — операция или функция получает аргумент неподходящего значения. К примеру, исключение возникает, если попытаться преобразовать строку в число.
print(int('привет')) # ValueError: invalid literal for int() with base 10: 'привет'
- IndexError — обращение к элементу по несуществующему индексу.
mylist = ['яблоко', 'банан', 'вишня']
print(mylist[10]) # IndexError: list index out of range
- ZeroDivisionError — деление числа на ноль.
a = 5
b = 0
print(a / b) # ZeroDivisionError: division by zero

Читайте также:
- FileNotFoundError — Python не может найти файл, который мы хотим открыть.
with open('non_existent_file.txt', 'r') as f:
print(f.read()) # FileNotFoundError: No such file or directory: 'non_existent_file.txt'
Это одни из наиболее часто встречающихся встроенных исключений в Python. Полный список можно посмотреть в официальной документации.
Обработка исключений в Python: try, except, finally, else и raise
В Python есть всё необходимое для создания собственных обработчиков исключений. Это полезно, если надо реализовать нетипичное для Python поведение, которое не предусмотрели разработчики. Для этого используются блоки try, except, finally, else и raise:
- С помощью блока try Python проверяет код на наличие исключений. Если в try встречается ошибка, выполнение переходит к первому блоку except.
try:
print(undefined_variable)

Читайте также:
- В блоке except содержится код, который будет выполняться, если в блоке try нашлась ошибка.
try:
print(undefined_variable)
except NameError:
print('Переменная не определена.')
- В finally помещают код, который будет выполняться независимо от того, была ли найдена ошибка или нет. Часто этот блок используют для работы с файлами, чтобы закрыть документ.
try:
f = open('my_file.txt')
# Действия с файлом
except FileNotFoundError:
print('Файл не найден.')
finally:
f.close()
- Код в else выполняется, если try не нашёл исключений.
try:
n = int(input('Введите число: '))
except ValueError:
print('Это не число!')
else:
print(f'Вы ввели число {n}')
- Ключевое слово as при обработке ошибок используется для присвоения исключению переменной. К примеру, напишем собственное исключение для обработки деления на ноль. Ошибку назовём ZeroDivisionError и присвоим переменной e. Теперь к ней можно получить доступ для печати названия ошибки в консоль.
try:
1/0
except ZeroDivisionError as e:
print(f'Исключение: {str(e)}') # Исключение: division by zero
- Команда raise в Python используется для принудительного вызова исключения. Это может быть полезно, если мы столкнулись с условием, которое должно остановить выполнение программы или вызвать ошибку.
raise ValueError('Недопустимое значение')
Обработка нескольких исключений
С помощью блоков except можно обрабатывать несколько исключений разными способами. Каждый except соответствует определённому типу ошибки.
Например, если в блоке try происходит исключение TypeError, будет выполнен первый блок except, и аналогично для ZeroDivisionError:
try:
n = '2' + 2
except TypeError:
print('Сообщение о TypeError')
except ZeroDivisionError:
print('Сообщение о ZeroDivisionError')
Иногда возникает необходимость обрабатывать несколько типов исключений одинаковым образом. В этом случае можно перечислить эти исключения в одном блоке except, разделив их запятыми:
try:
n = '1' + 1 # Код, который может вызвать исключение TypeError или ZeroDivisionError
except (TypeError, ZeroDivisionError):
print('Обнаружена ошибка TypeError или ZeroDivisionError')
Блок except без указания конкретного типа исключения будет обрабатывать все исключения, которые не были обработаны в предыдущих блоках except, в том числе прерывание с клавиатуры, системный выход и другое.
Такая форма конструкции практически не используется. Вместо этого разработчики предпочитают except Exception. Так можно сначала обработать конкретные исключения, а потом уже всё остальное.
К примеру, в первом блоке except обработаем исключение TypeError — будем выводить в консоль сообщение Обнаружена ошибка TypeError. Второй блок except будет отлавливать остальные исключения и выводить Что-то пошло не так:
try:
n = '1' + 1 # Код, который может вызвать исключение
except TypeError:
print('Обнаружена ошибка TypeError')
except Exception:
print('Что-то пошло не так')
# Результат: "Обнаружена ошибка TypeError"
Начинать обработку следует с более узких классов исключений, например TypeError. Если начать с более широкого класса, такого как Exception, то всегда будет срабатывать первый блок except:
try:
n = '1' + 1 # Код, который может вызвать исключение
except Exception:
print('Что-то пошло не так')
except TypeError:
print('Обнаружена ошибка TypeError')
# Результат: "Что-то пошло не так"
Как игнорировать ошибки в Python
Вот написали вы код, запускаете его, а Python сыпет ошибки в консоль. Вы уверены, что это фичи, а не баги, но это надо как-то донести до интерпретатора. Для таких случаев в Python есть механизм игнорирования ошибок.

Читайте также:
Для игнорирования исключений в Python можно использовать блок try/except. При этом нужно оставить его пустым или записать в нём оператор-заглушку pass, который ничего не делает.
В Python нельзя делить на ноль, но с помощью механизма игнорирования ошибок этого можно избежать. Попробуем разделить единицу на каждое число из списка. Заметьте, что среди чисел есть ноль, поэтому выполнение должно вызвать исключения ZeroDivisionError.

Читайте также:
Мы же обработаем эту ошибку с помощью блока except. Используем оператор pass, чтобы Python проигнорировал ошибку:
lst = [4, 2, 0, -1, -3]
for j in lst:
try:
print(1/j) # Вызовет ZeroDivisionError
except ZeroDivisionError:
pass
В результате получим:
0.25
0.5
-1.0
-0.3333333333333333
Важно помнить, что не стоит полностью игнорировать ошибки. Это может привести к тому, что скрытые проблемы в коде останутся незамеченными и будет сложнее отлаживать и поддерживать программу в долгосрочной перспективе.
Лучше обрабатывать ошибки должным образом: исправлять проблему, выводить полезное сообщение об ошибке или отлавливать конкретные исключения.
Создание собственных исключений
Иногда надо реализовать собственный обработчик ошибок с помощью исключений. Это делает код более безопасным и поддерживаемым. Для создания собственного исключения достаточно определить новый класс, который наследуется от базового класса Exception или от любого другого встроенного исключения:
class ValidationError(Exception):
pass

Читайте также:
В примере выше ValidationError — исключение, которое не делает ничего, кроме наследования поведения стандартного исключения Exception.
Продолжим код:
class ValidationError(Exception):
pass
def person_age(age):
if age < 0:
raise ValidationError('Возраст не может быть отрицательным')
elif age > 120:
raise ValidationError('Возраст не может быть больше 120')
return True
try:
person_age(150)
except ValidationError as e:
print(e)
В этом примере функция person_age использует созданное нами исключение ValidationError для проверки корректности введённого возраста. Вызов функции с некорректными данными приводит к генерации исключения ValidationError, которое затем можно перехватить и обработать.
Что в итоге
Исключения в Python полезны для написания отказоустойчивого кода. Они позволяют обрабатывать ошибки при выполнении программы и гарантировать, что код продолжит свою работу даже в неожиданных ситуациях. Однако важно понимать, что не все ошибки можно или нужно обрабатывать, — иногда лучше позволить программе завершиться с ошибкой, чтобы можно было быстро узнать о проблеме и исправить её.