Код
#статьи

Исключения в 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 полезны для написания отказоустойчивого кода. Они позволяют обрабатывать ошибки при выполнении программы и гарантировать, что код продолжит свою работу даже в неожиданных ситуациях. Однако важно понимать, что не все ошибки можно или нужно обрабатывать, — иногда лучше позволить программе завершиться с ошибкой, чтобы можно было быстро узнать о проблеме и исправить её.

Изучайте IT на практике — бесплатно

Курсы за 2990 0 р.

Я не знаю, с чего начать
Научитесь: Профессия Python-разработчик Узнать больше
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована