Объект ошибки python

Содержание:развернуть

  • Как устроен механизм исключений
  • Как обрабатывать исключения в Python (try except)
  • As — сохраняет ошибку в переменную

  • Finally — выполняется всегда

  • Else — выполняется когда исключение не было вызвано

  • Несколько блоков except

  • Несколько типов исключений в одном блоке except

  • Raise — самостоятельный вызов исключений

  • Как пропустить ошибку

  • Исключения в lambda функциях
  • 20 типов встроенных исключений в Python
  • Как создать свой тип Exception

Программа, написанная на языке Python, останавливается сразу как обнаружит ошибку. Ошибки могут быть (как минимум) двух типов:

  • Синтаксические ошибки — возникают, когда написанное выражение не соответствует правилам языка (например, написана лишняя скобка);
  • Исключения — возникают во время выполнения программы (например, при делении на ноль).

Синтаксические ошибки исправить просто (если вы используете IDE, он их подсветит). А вот с исключениями всё немного сложнее — не всегда при написании программы можно сказать возникнет или нет в данном месте исключение. Чтобы приложение продолжило работу при возникновении проблем, такие ошибки нужно перехватывать и обрабатывать с помощью блока try/except.

Как устроен механизм исключений

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

💁‍♂️ Пример: напишем скрипт, в котором функция ожидает число, а мы передаём сроку (это вызовет исключение «TypeError»):

def b(value):
print("-> b")
print(value + 1) # ошибка тут

def a(value):
print("-> a")
b(value)

a("10")

> -> a
> -> b
> Traceback (most recent call last):
> File "test.py", line 11, in <module>
> a("10")
> File "test.py", line 8, in a
> b(value)
> File "test.py", line 3, in b
> print(value + 1)
> TypeError: can only concatenate str (not "int") to str

В данном примере мы запускаем файл «test.py» (через консоль). Вызывается функция «a«, внутри которой вызывается функция «b«. Все работает хорошо до сточки print(value + 1). Тут интерпретатор понимает, что нельзя конкатенировать строку с числом, останавливает выполнение программы и вызывает исключение «TypeError».

Далее ошибка передается по цепочке в обратном направлении: «b» → «a» → «test.py«. Так как в данном примере мы не позаботились обработать эту ошибку, вся информация по ошибке отобразится в консоли в виде Traceback.

Traceback (трассировка) — это отчёт, содержащий вызовы функций, выполненные в определенный момент. Трассировка помогает узнать, что пошло не так и в каком месте это произошло.

Traceback лучше читать снизу вверх ↑

Пример Traceback в Python

В нашем примере Traceback содержится следующую информацию (читаем снизу вверх):

  1. TypeError — тип ошибки (означает, что операция не может быть выполнена с переменной этого типа);
  2. can only concatenate str (not "int") to str — подробное описание ошибки (конкатенировать можно только строку со строкой);
  3. Стек вызова функций (1-я линия — место, 2-я линия — код). В нашем примере видно, что в файле «test.py» на 11-й линии был вызов функции «a» со строковым аргументом «10». Далее был вызов функции «b». print(value + 1) это последнее, что было выполнено — тут и произошла ошибка.
  4. most recent call last — означает, что самый последний вызов будет отображаться последним в стеке (в нашем примере последним выполнился print(value + 1)).

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

Как обрабатывать исключения в Python (try except)

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

Например, вот как можно обработать ошибку деления на ноль:

try:
a = 7 / 0
except:
print('Ошибка! Деление на 0')

Здесь в блоке try находится код a = 7 / 0 — при попытке его выполнить возникнет исключение и выполнится код в блоке except (то есть будет выведено сообщение «Ошибка! Деление на 0»). После этого программа продолжит свое выполнение.

💭 PEP 8 рекомендует, по возможности, указывать конкретный тип исключения после ключевого слова except (чтобы перехватывать и обрабатывать конкретные исключения):

try:
a = 7 / 0
except ZeroDivisionError:
print('Ошибка! Деление на 0')

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

try:
a = 7 / 0
except Exception:
print('Любая ошибка!')

As — сохраняет ошибку в переменную

Перехваченная ошибка представляет собой объект класса, унаследованного от «BaseException». С помощью ключевого слова as можно записать этот объект в переменную, чтобы обратиться к нему внутри блока except:

try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(e)

> [Errno 2] No such file or directory: 'ok123.txt'

В примере выше мы обращаемся к объекту класса «FileNotFoundError» (при выводе на экран через print отобразится строка с полным описанием ошибки).

У каждого объекта есть поля, к которым можно обращаться (например если нужно логировать ошибку в собственном формате):

import datetime

now = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S")

try:
file = open('ok123.txt', 'r')
except FileNotFoundError as e:
print(f"{now} [FileNotFoundError]: {e.strerror}, filename: {e.filename}")

> 20-11-2021 18:42:01 [FileNotFoundError]: No such file or directory, filename: ok123.txt

Finally — выполняется всегда

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

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

В следующем примере откроем файл и обратимся к несуществующей строке:

file = open('ok.txt', 'r')

try:
lines = file.readlines()
print(lines[5])
finally:
file.close()
if file.closed:
print("файл закрыт!")

> файл закрыт!
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> print(lines[5])
> IndexError: list index out of range

Даже после исключения «IndexError», сработал код в секции finally, который закрыл файл.

p.s. данный пример создан для демонстрации, в реальном проекте для работы с файлами лучше использовать менеджер контекста with.

Также можно использовать одновременно три блока try/except/finally. В этом случае:

  • в try — код, который может вызвать исключения;
  • в except — код, который должен выполниться при возникновении исключения;
  • в finally — код, который должен выполниться в любом случае.

def sum(a, b):
res = 0

try:
res = a + b
except TypeError:
res = int(a) + int(b)
finally:
print(f"a = {a}, b = {b}, res = {res}")

sum(1, "2")

> a = 1, b = 2, res = 3

Else — выполняется когда исключение не было вызвано

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

Допустим нужно вывести результат деления двух чисел и обработать исключения в случае попытки деления на ноль:

b = int(input('b = '))
c = int(input('c = '))
try:
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
else:
print(f"a = {a}")

> b = 10
> c = 1
> a = 10.0

В этом случае, если пользователь присвоит переменной «с» ноль, то появится исключение и будет выведено сообщение «‘Ошибка! Деление на 0′», а код внутри блока else выполняться не будет. Если ошибки не будет, то на экране появятся результаты деления.

Несколько блоков except

В программе может возникнуть несколько исключений, например:

  1. Ошибка преобразования введенных значений к типу float («ValueError»);
  2. Деление на ноль («ZeroDivisionError»).

В Python, чтобы по-разному обрабатывать разные типы ошибок, создают несколько блоков except:

try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except ZeroDivisionError:
print('Ошибка! Деление на 0')
except ValueError:
print('Число введено неверно')
else:
print(f"a = {a}")

> b = 10
> c = 0
> Ошибка! Деление на 0

> b = 10
> c = питон
> Число введено неверно

Теперь для разных типов ошибок есть свой обработчик.

Несколько типов исключений в одном блоке except

Можно также обрабатывать в одном блоке except сразу несколько исключений. Для этого они записываются в круглых скобках, через запятую сразу после ключевого слова except. Чтобы обработать сообщения «ZeroDivisionError» и «ValueError» в одном блоке записываем их следующим образом:

try:
b = float(input('b = '))
c = float(input('c = '))
a = b / c
except (ZeroDivisionError, ValueError) as er:
print(er)
else:
print('a = ', a)

При этом переменной er присваивается объект того исключения, которое было вызвано. В результате на экран выводятся сведения о конкретной ошибке.

Raise — самостоятельный вызов исключений

Исключения можно генерировать самостоятельно — для этого нужно запустить оператор raise.

min = 100
if min > 10:
raise Exception('min must be less than 10')

> Traceback (most recent call last):
> File "test.py", line 3, in <module>
> raise Exception('min value must be less than 10')
> Exception: min must be less than 10

Перехватываются такие сообщения точно так же, как и остальные:

min = 100

try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')

> Моя ошибка

Кроме того, ошибку можно обработать в блоке except и пробросить дальше (вверх по стеку) с помощью raise:

min = 100

try:
if min > 10:
raise Exception('min must be less than 10')
except Exception:
print('Моя ошибка')
raise

> Моя ошибка
> Traceback (most recent call last):
> File "test.py", line 5, in <module>
> raise Exception('min must be less than 10')
> Exception: min must be less than 10

Как пропустить ошибку

Иногда ошибку обрабатывать не нужно. В этом случае ее можно пропустить с помощью pass:

try:
a = 7 / 0
except ZeroDivisionError:
pass

Исключения в lambda функциях

Обрабатывать исключения внутри lambda функций нельзя (так как lambda записывается в виде одного выражения). В этом случае нужно использовать именованную функцию.

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

Иерархия классов для встроенных исключений в Python выглядит так:

BaseException
SystemExit
KeyboardInterrupt
GeneratorExit
Exception
ArithmeticError
AssertionError
...
...
...
ValueError
Warning

Все исключения в Python наследуются от базового BaseException:

  • SystemExit — системное исключение, вызываемое функцией sys.exit() во время выхода из приложения;
  • KeyboardInterrupt — возникает при завершении программы пользователем (чаще всего при нажатии клавиш Ctrl+C);
  • GeneratorExit — вызывается методом close объекта generator;
  • Exception — исключения, которые можно и нужно обрабатывать (предыдущие были системными и их трогать не рекомендуется).

От Exception наследуются:

1 StopIteration — вызывается функцией next в том случае если в итераторе закончились элементы;

2 ArithmeticError — ошибки, возникающие при вычислении, бывают следующие типы:

  • FloatingPointError — ошибки при выполнении вычислений с плавающей точкой (встречаются редко);
  • OverflowError — результат вычислений большой для текущего представления (не появляется при операциях с целыми числами, но может появиться в некоторых других случаях);
  • ZeroDivisionError — возникает при попытке деления на ноль.

3 AssertionError — выражение, используемое в функции assert неверно;

4 AttributeError — у объекта отсутствует нужный атрибут;

5 BufferError — операция, для выполнения которой требуется буфер, не выполнена;

6 EOFError — ошибка чтения из файла;

7 ImportError — ошибка импортирования модуля;

8 LookupError — неверный индекс, делится на два типа:

  • IndexError — индекс выходит за пределы диапазона элементов;
  • KeyError — индекс отсутствует (для словарей, множеств и подобных объектов);

9 MemoryError — память переполнена;

10 NameError — отсутствует переменная с данным именем;

11 OSError — исключения, генерируемые операционной системой:

  • ChildProcessError — ошибки, связанные с выполнением дочернего процесса;
  • ConnectionError — исключения связанные с подключениями (BrokenPipeError, ConnectionResetError, ConnectionRefusedError, ConnectionAbortedError);
  • FileExistsError — возникает при попытке создания уже существующего файла или директории;
  • FileNotFoundError — генерируется при попытке обращения к несуществующему файлу;
  • InterruptedError — возникает в том случае если системный вызов был прерван внешним сигналом;
  • IsADirectoryError — программа обращается к файлу, а это директория;
  • NotADirectoryError — приложение обращается к директории, а это файл;
  • PermissionError — прав доступа недостаточно для выполнения операции;
  • ProcessLookupError — процесс, к которому обращается приложение не запущен или отсутствует;
  • TimeoutError — время ожидания истекло;

12 ReferenceError — попытка доступа к объекту с помощью слабой ссылки, когда объект не существует;

13 RuntimeError — генерируется в случае, когда исключение не может быть классифицировано или не подпадает под любую другую категорию;

14 NotImplementedError — абстрактные методы класса нуждаются в переопределении;

15 SyntaxError — ошибка синтаксиса;

16 SystemError — сигнализирует о внутренне ошибке;

17 TypeError — операция не может быть выполнена с переменной этого типа;

18 ValueError — возникает когда в функцию передается объект правильного типа, но имеющий некорректное значение;

19 UnicodeError — исключение связанное с кодирование текста в unicode, бывает трех видов:

  • UnicodeEncodeError — ошибка кодирования;
  • UnicodeDecodeError — ошибка декодирования;
  • UnicodeTranslateError — ошибка перевода unicode.

20 Warning — предупреждение, некритическая ошибка.

💭 Посмотреть всю цепочку наследования конкретного типа исключения можно с помощью модуля inspect:

import inspect

print(inspect.getmro(TimeoutError))

> (<class 'TimeoutError'>, <class 'OSError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)

📄 Подробное описание всех классов встроенных исключений в Python смотрите в официальной документации.

Как создать свой тип Exception

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

class MyError(Exception):
def __init__(self, text):
self.txt = text

try:
raise MyError('Моя ошибка')
except MyError as er:
print(er)

> Моя ошибка


С помощью try/except контролируются и обрабатываются ошибки в приложении. Это особенно актуально для критически важных частей программы, где любые «падения» недопустимы (или могут привести к негативным последствиям). Например, если программа работает как «демон», падение приведет к полной остановке её работы. Или, например, при временном сбое соединения с базой данных, программа также прервёт своё выполнение (хотя можно было отловить ошибку и попробовать соединиться в БД заново).

Вместе с try/except можно использовать дополнительные блоки. Если использовать все блоки описанные в статье, то код будет выглядеть так:

try:
# попробуем что-то сделать
except (ZeroDivisionError, ValueError) as e:
# обрабатываем исключения типа ZeroDivisionError или ValueError
except Exception as e:
# исключение не ZeroDivisionError и не ValueError
# поэтому обрабатываем исключение общего типа (унаследованное от Exception)
# сюда не сходят исключения типа GeneratorExit, KeyboardInterrupt, SystemExit
else:
# этот блок выполняется, если нет исключений
# если в этом блоке сделать return, он не будет вызван, пока не выполнился блок finally
finally:
# этот блок выполняется всегда, даже если нет исключений else будет проигнорирован
# если в этом блоке сделать return, то return в блоке

Подробнее о работе с исключениями в Python можно ознакомиться в официальной документации.

Обработка исключений

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

Напишем программу, которая будет считать обратные значения для целых чисел из заданного диапазона и выводить их в одну строку с разделителем ‘;’. Один из вариантов кода для решения этой задачи выглядит так:

print(";".join(str(1 / x) for x in range(int(input()), int(input()) + 1)))

Программа получилась в одну строчку за счёт использования списочных выражений. Однако при вводе диапазона чисел, включающего в себя 0 (например, от -1 до 1), программа выдаст следующую ошибку:

ZeroDivisionError: division by zero

В программе произошла ошибка «деление на ноль». Такая ошибка, возникающая при выполнении программы и останавливающая её работу, называется исключением.

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

interval = range(int(input()), int(input()) + 1)
if 0 in interval:
    print("Диапазон чисел содержит 0.")
else:
    print(";".join(str(1 / x) for x in interval))

Теперь для диапазона, включающего в себя 0, например от -2 до 2, исключения ZeroDivisionError не возникнет. Однако при вводе строки, которую невозможно преобразовать в целое число (например, «a»), будет вызвано другое исключение:

ValueError: invalid literal for int() with base 10: 'a'

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

start = input()
end = input()
# Метод lstrip("-"), удаляющий символы "-" в начале строки, нужен для учёта
# отрицательных чисел, иначе isdigit() вернёт для них False
if not (start.lstrip("-").isdigit() and end.lstrip("-").isdigit()):
    print("
    ввести два числа.")
else:
    interval = range(int(start), int(end) + 1)
    if 0 in interval:
        print("Диапазон чисел содержит 0.")
    else:
        print(";".join(str(1 / x) for x in interval))

Теперь наша программа работает без ошибок и при вводе строк, которые нельзя преобразовать в целое число.

Подход, который был нами применён для предотвращения ошибок, называется Look Before You Leap (LBYL), или «Посмотри перед прыжком». В программе, реализующей такой подход, проверяются возможные условия возникновения ошибок до исполнения основного кода.

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

Существует другой подход для работы с ошибками: Easier to Ask Forgiveness than Permission (EAFP), или «Проще попросить прощения, чем разрешения». В этом подходе сначала исполняется код, а в случае возникновения ошибок происходит их обработка. Подход EAFP реализован в Python в виде обработки исключений.

Исключения в Python являются классами ошибок. В Python есть много стандартных исключений. Они имеют определённую иерархию за счёт механизма наследования классов. В документации Python версии 3.10.8 приводится следующее дерево иерархии стандартных исключений:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- EncodingWarning
           +-- ResourceWarning

Для обработки исключения в Python используется следующий синтаксис:

try:
    <код , который может вызвать исключения при выполнении>
except <классисключения_1>:
    <код обработки исключения>
except <классисключения_2>:
    <код обработки исключения>
...
else:
    <код выполняется, если не вызвано исключение в блоке try>
finally:
    <код , который выполняется всегда>

Блок try содержит код, в котором нужно обработать исключения, если они возникнут.
При возникновении исключения интерпретатор последовательно проверяет, в каком из блоков except обрабатывается это исключение.
Исключение обрабатывается в первом блоке except, обрабатывающем класс этого исключения или базовый класс возникшего исключения.
Необходимо учитывать иерархию исключений для определения порядка их обработки в блоках except. Начинать обработку исключений следует с более узких классов исключений. Если начать с более широкого класса исключения, например Exception, то всегда при возникновении исключения будет срабатывать первый блок except.
Сравните два следующих примера. В первом порядок обработки исключений указан от производных классов к базовым, а во втором — наоборот.

Первый пример:

try:
    print(1 / int(input()))
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")
except Exception:
    print("Неизвестная ошибка.")

При вводе значений «0» и «a» получим ожидаемый, соответствующий возникающим исключениям вывод:

Невозможно преобразовать строку в число.

и

Ошибка деления на ноль.

Второй пример:

try:
    print(1 / int(input()))
except Exception:
    print("Неизвестная ошибка.")
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")

При вводе значений «0» и «a» получим в обоих случаях неинформативный вывод:

Неизвестная ошибка.

Необязательный блок else выполняет код в случае, если в блоке try не вызвано исключение. Добавим блок else в пример для вывода сообщения об успешном выполнении операции:

try:
    print(1 / int(input()))
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")
except Exception:
    print("Неизвестная ошибка.")
else:
    print("Операция выполнена успешно.")

Теперь при вводе корректного значения, например «5», вывод программы будет следующим:

0.2
Операция выполнена успешно.

Блок finally выполняется всегда, даже если возникло какое-то исключение, не учтённое в блоках except, или код в этих блоках сам вызвал какое-либо исключение. Добавим в нашу программу вывод строки «Программа завершена» в конце программы даже при возникновении исключений:

try:
    print(1 / int(input()))
except ZeroDivisionError:
    print("Ошибка деления на ноль.")
except ValueError:
    print("Невозможно преобразовать строку в число.")
except Exception:
    print("Неизвестная ошибка.")
else:
    print("Операция выполнена успешно.")
finally:
    print("Программа завершена.")

Перепишем код, созданный с применением подхода LBYL, для первого примера из этого параграфа с использованием обработки исключений:

try:
    print(";".join(str(1 / x) for x in range(int(input()), int(input()) + 1)))
except ZeroDivisionError:
    print("Диапазон чисел содержит 0.")
except ValueError:
    print("Необходимо ввести два числа.")

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

Исключения можно принудительно вызывать с помощью оператора raise. Этот оператор имеет следующий синтаксис:

raise <класс исключения>(параметры)

В качестве параметра можно, например, передать строку с сообщением об ошибке.

Создание собственных исключений

В Python можно создавать свои собственные исключения. Синтаксис создания исключения такой же, как и у создания класса. При создании исключения его необходимо наследовать от какого-либо стандартного класса-исключения.

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

  • NumbersError — базовый класс исключения;
  • EvenError — исключение, которое вызывается при наличии хотя бы одного чётного числа;
  • NegativeError — исключение, которое вызывается при наличии хотя бы одного отрицательного числа.
class NumbersError(Exception):
    pass


class EvenError(NumbersError):
    pass


class NegativeError(NumbersError):
    pass


def no_even(numbers):
    if all(x % 2 != 0 for x in numbers):
        return True
    raise EvenError("В списке не должно быть чётных чисел")


def no_negative(numbers):
    if all(x >= 0 for x in numbers):
        return True
    raise NegativeError("В списке не должно быть отрицательных чисел")


def main():
    print("Введите числа в одну строку через пробел:")
    try:
        numbers = [int(x) for x in input().split()]
        if no_negative(numbers) and no_even(numbers):
            print(f"Сумма чисел равна: {sum(numbers)}.")
    except NumbersError as e:  # обращение к исключению как к объекту
        print(f"Произошла ошибка: {e}.")
    except Exception as e:
        print(f"Произошла непредвиденная ошибка: {e}.")

        
if __name__ == "__main__":
    main()

Модули

Обратите внимание: в программе основной код выделен в функцию main. А код вне функций содержит только условный оператор и вызов функции main при выполнении условия __name__ == "__main__". Это условие проверяет, запущен ли файл как самостоятельная программа или импортирован как модуль.

Любая программа, написанная на языке программирования Python, может быть импортирована как модуль в другую программу. В идеологии Python импортировать модуль — значит полностью его выполнить. Если основной код модуля содержит вызовы функций, ввод или вывод данных без использования указанного условия __name__ == "__main__", то произойдёт полноценный запуск программы. А это не всегда удобно, если из модуля нужна только отдельная функция или какой-либо класс.

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

Для импорта модуля из файла, например example_module.py, нужно указать его имя, если он находится в той же папке, что и импортирующая его программа:

import example_module

Если требуется отдельный компонент модуля, например функция или класс, то импорт можно осуществить так:

from example_module import some_function, ExampleClass

Обратите внимание: при втором способе импортированные объекты попадают в пространство имён новой программы. Это означает, что они будут объектами новой программы и в программе не должно быть других объектов с такими же именами.

Рассмотрим написанное выше на примере. Пусть имеется программа module_hello.py, в которой находится функция hello(name), возвращающая строку приветствия пользователя по имени. В самой программе кроме функции присутствует вызов этой функции и печать результата её работы. Импортируем из модуля module_hello.py функцию hello(name) в другую программу program.py и также используем для вывода приветствия пользователя.

Код программы module_hello.py:

def hello(name):
    return f"Привет, {name}!"


print(hello(input("Введите своё имя: ")))

Код программы program.py:

from module_hello import hello

print(hello(input("Добрый день. Введите имя: ")))

При выполнении program.py нас ожидает неожиданное действие. Программа сначала запросит имя пользователя, а затем сделает это ещё раз, но с приветствием из program.py.

Введите своё имя: Андрей
Привет, Андрей!
Добрый день. Введите имя: Андрей
Привет, Андрей!

Наша ошибка заключается в том, что программа module_hello.py выполняется полностью, включая основной код с вызовом функции и выводом результата. Исправим программу module_hello.py, добавив проверку, запущена программа или импортирована как модуль:

def hello(name):
    return f"Привет, {name}!"


if __name__ == "__main__":
    print(hello(input("Введите своё имя: ")))

Теперь при импорте модуля module_hello.py код в теле условного оператора выполняться не будет. А основной код этой программы выполнится только при запуске файла как отдельной программы.
Для большего удобства обычно в теле указанного условного оператора вызывают функцию main(), а основной код программы оформляют уже внутри этой функции.
Тогда наш модуль можно переписать так:

def hello(name):
    return f"Привет, {name}!"


def main():
    print(hello(input("Введите своё имя: ")))


if __name__ == "__main__":
    main()

Обратите внимание: при импорте модуля мы можем с помощью символа * указать, что необходимо импортировать все объекты. Например, так:

from some_module import *

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

Исключения#

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

Тем не менее большинство ошибок программой не обрабатывается и их возникновение приводит к выводу сообщения об ошибке. В качестве примера попробуем поделить на ноль.

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_13252/2354412189.py in <module>
----> 1 1/0

ZeroDivisionError: division by zero

Как видимо, появилось сообщение об ошибке. Последняя строка этого сообщения, говорит, что конкретно произошло.

  • Исключения бывают разных типов и конкретный тип брошенного исключения печатается до двоеточия. В данном случае это ZeroDivisionError.

  • После типа ошибки выводится дополнительная информация, которая может зависеть от типа ошибки и причины её возникновения.

  • Всё, что идёт до последней строки, демонстрирует в каком контексте возникла ошибка в виде трассировочных данных. Более наглядное представление о трассировочной информации даёт пример, когда ошибка возникает внутри нескольких функций.

def f():
    1/0

def g():
    f()

g()
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_13252/4200539898.py in <module>
      5     f()
      6 
----> 7 g()

~\AppData\Local\Temp/ipykernel_13252/4200539898.py in g()
      3 
      4 def g():
----> 5     f()
      6 
      7 g()

~\AppData\Local\Temp/ipykernel_13252/4200539898.py in f()
      1 def f():
----> 2     1/0
      3 
      4 def g():
      5     f()

ZeroDivisionError: division by zero

В этот раз сообщение об ошибке гораздо длиннее, потому что ошибка возникла не на самом верхнем уровне, а на уровне функции.

Встроенные исключения#

В python есть приличное количество встроенных исключений, которые интерпретатор бросает при обнаружении ошибки во время исполнения. Например, если попытаться обратиться к необъявленной переменной (несуществующему имени), то возникнет ошибка NameError. Обращение к неправильному атрибуту вызовет ошибку AttributeError, обращение за пределы списка — IndexError, поиск в словаре по несуществующему ключу — KeyError и т.д.

В ряде ситуаций python возбуждает исключение не в результате ошибки, а в служебных целях. Например, как обсуждалось ранее, функции работающие с итерируемыми объектами ожидают исключение StopIteration, т.е. появление такого исключения — не является исключительной ситуацией, связанной с логической ошибкой в коде, а лишь механизм, которым итерируемый объект сообщает клиентскому коду, что элементы в нем исчерпались.

В python всё является объектом, в том числе и исключения. При этом у каждого исключения есть свой тип, примеры которых уже обсуждалось выше: NameError, AttributeError, StopIteration и т.д. — это всё тип исключения. Обнаружение ошибки приводит к тому, что создаётся экземпляр исключения соответствующего типа и это исключение начинает распространяться. При этом все встроенные типы исключений находятся в иерархии, с которой можно ознакомиться по ссылке.

Так, на самом верху располагается базовый класс BaseException от которого наследуется все остальные типы исключений. На втором этаже располагаются исключения связанные с завершением исполнения программы (например, SystemExit возникает при вызове функции sys.exit(), KeyboardInterrupt возникает, если пользователь нажимает прерывающую комбинацию клавиш во время работы приложения), а так же класс Exception, который и является базовым классом для всех исключений не связанных с завершением программы.

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

Возбуждение исключений. raise#

Многие исключения бросаются интерпретатором python при обнаружении ошибки или в служебных целях. Для этого используется ключевое слово raise справа от которого указывается экземпляр исключения.

raise Exception("Моё первое исключение!")
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_13252/2359356075.py in <module>
----> 1 raise Exception("Моё первое исключение!")

Exception: Моё первое исключение!

Когда исключение возбужденно, оно начинает распространяться, т.е. нормальное исполнение программы прекращается. Так, например, покинуть функцию можно не только используя ключевое слово return, но и ключевым словом raise.

def f():
    print("До исключения!")
    raise Exception("Исключение!")
    print("После исключения!")

f()
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_13252/2985915162.py in <module>
      4     print("После исключения!")
      5 
----> 6 f()

~\AppData\Local\Temp/ipykernel_13252/2985915162.py in f()
      1 def f():
      2     print("До исключения!")
----> 3     raise Exception("Исключение!")
      4     print("После исключения!")
      5 

Exception: Исключение!

Видим, что инструкция print("До исключения!") исполнилась, а инструкция print("После исключения") — нет, т.к. сразу после возбуждения исключения нормальный поток исполнения программы прекратился и исключение начало распространяться. Исключение распространяется наверх по стеку вызовов, пока не встретится блок, обрабатывающий это исключение. Если такой блок так и не встречается, то выводится сообщение об ошибке и программа падает (или может продолжить ожидание инструкций от пользователя в интерактивном режиме, что происходит, например, в jupyter ноутбуках).

Обработка исключений.#

Обработка ошибок в python осуществляется с помощью инструкции try, которая может встречаться в блоках двух видов:

  • try/except;

  • try/finally;

Блок try/except#

С помощью блока try/except можно обрабатывать конкретные типы исключений. В самой простой своей форме он выглядит примерно следующим образом.

try:
    инструкции
except ТипИсключения:
    инструкции

После ключевого слова try помещаются инструкции, исполнение которых может привести к возникновению исключения. Инструкции в блоке except выполняются только, если а) вовремя выполнения инструкций в блоке try возникла ошибка и б) её тип совпал с указанным типом.

В качестве примера опять поделим на ноль, но в этот раз поместим это деление в блок try/except и обработаем исключение типа ZeroDivisionError.

try:
    1/0
    print("После попытки деления.")
except ZeroDivisionError:
    print("Перехвачена ошибка деления на ноль!")
Перехвачена ошибка деления на ноль!

Видим, что

  1. сообщения об ошибке с типом ошибки и трассировочной информацией не появилось, т.к. ошибка была обработана;

  2. вместо этого выполнилась инструкция print в блоке except ZeroDivisionError;

  3. инструкция print в блоке try после деления на ноль не выполнилась, т.к. ошибка возникла раньше и начала распространяться.

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

Приведем несколько примеров, чтобы продемонстрировать этот механизм.

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

try:
    try:
        1/0
    except NameError:
        print("Ошибка перехвачена внутренним блоком try/except.")
except ZeroDivisionError:
    print("Ошибка перехвачена внешним блоком try/except.")

print("Инструкция после обработки исключения.")
Ошибка перехвачена внешним блоком try/except.
Инструкция после обработки исключения.

Здесь исключение ZeroDivisionError возникло во внутреннем блоке try/except, но тип возникшего исключения не совпал с NameError. В итоге внутренний блок try/except ошибку не обработало и она продолжила распространяться, в результате чего попала во внешний блок try/except, который обрабатывает ошибки типа ZeroDivisionError, что привело прекращению распространения ошибки и к исполнению инструкции print во внешнем блоке. Далее программа продолжает исполняться в нормальном режиме.

Следующий пример демонстрирует, что исключение прекращает распространяться, как только встретит правильный блок except.

try:
    try:
        1/0
    except ZeroDivisionError:
        print("Ошибка перехвачена внутренним блоком except.")
except ZeroDivisionError:
    print("Ошибка перехвачена внешним блоком except.")

print("Инструкция после обработки исключения.")
Ошибка перехвачена внутренним блоком except.
Инструкция после обработки исключения.

Здесь внутренний блок try/except обрабатывает правильный тип исключения. Возникшее исключение прекращает распространяться в этом внутреннем блоке try/except и выполняется инструкция print во внутреннем блоке except. Т.к. исполнение инструкций после внешнего ключевого слова try проходит без ошибок (ошибка возникает, но внешний блок её не замечает, т.к. она обрабатывается внутренним блоком try/except), то внешний блок except игнорируется.

Блоков except может быть несколько в одном блоке try/except, что позволяет по-разному реагировать на ошибки разных типов.

try:
    1/0
except NameError:
    print("Перехвачена ошибка NameError")
except ZeroDivisionError:
    print("Перехвачена ошибка ZeroDivisionError")
Перехвачена ошибка ZeroDivisionError

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

Tip

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

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

try:
    1/0
except Exception:
    print("Перехвачено неожиданное исключение!")
except ZeroDivisionError:
    print("Перехвачено исключение ZeroDivisionError!")
Перехвачено неожиданное исключение!

Здесь в блоке try возникает исключение ZeroDivisionError и начинает распространяться. В блоке except Exception проверка на тип успешно проходит (иерархия между ними выглядит так: Exception \(\to\) ArithmeticError \(\to\) ZeroDivisionError), а значит вызывается инструкции в соответствующем блоке, а до блока except ZeroDivisionError дело так и не доходит: ошибка обработана ранее.

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

try:
    1/0
except ZeroDivisionError:
    print("Перехвачено исключение ZeroDivisionError!")
except Exception:
    print("Перехвачено неожиданное исключение!")
Перехвачено исключение ZeroDivisionError!

Дополнительные возможности except#

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

except ТипИсключения as цель:
    операции над цель

Из некоторых объектов исключений можно получить дополнительную информацию об контексте, при которым они возбудились. У всех стандартных исключений преобразование к строке определенно таким образом, чтобы выводить сообщение, указанное при создании.

try:
    raise Exception("Сообщение исключения!")
except Exception as e:
    print(e)

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

try:
    1/0
except (ZeroDivisionError, NameError):
    print("Возникло исключение ZeroDivisionError или NameError!")
Возникло исключение ZeroDivisionError или NameError!

Так же, в самом конце блока try/except можно добавить блок else, инструкции в котором исполняться, только если блок try завершится без ошибок. Чтобы продемонстрировать это, определим функцию divide, которая будет пытаться делить первый аргумент на второй и возвращать результат деления, если деление произошло успешно, и возвращать бесконечность, если второй параметр равен нулю.

def divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("Попытка деления на ноль.")
        return float("inf")
    else:
        print("Деление произошло успешно.")
        return result

Блок else в этой функции исполнится, только если в блоке try деление произойдет успешно, т.е. если не возникнет ошибки деления на ноль.

print(divide(42, 14))
print(divide(42, 0))
Деление произошло успешно.
3.0
Попытка деления на ноль.
inf

Блок try/finally#

Синтаксис блока try/except выглядит следующим образом.

try:
    инструкции
finally:
    инструкции

Инструкции в блоке finally исполнятся в не зависимости от того, возникнет ли ошибка в блоке try или нет. Если ошибка все же возникает, то выполняется код в блоке finally и ошибка продолжает распространяться.

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

Например, раньше часто встречались конструкции следующего вида.

f = open(some_file, "w")
try:
    do_something_with_file(f)
finally:
    f.close()

Т.е. операции над открытым файлом производились в блоке try/finally, что гарантировало, что файл будет закрыт, даже если возникнет исключение. Сегодня гораздо удобнее использовать контекстные менеджеры with для таких целей.

Блок try/except/finally#

Если встречается блок обработки ошибки, в котором встречаются и except и finally, то его можно представить в виде вложения блока try/except внутрь блока try/finally. Иными словами, следующие две конструкции ведут себя одинаково.

try:
    инструкции
except SomeException:
    инструкции
else:
    инструкции
finally:
    инструкции
try:
    try:
        инструкции
    except SomeException:
        инструкции
    else:
        инструкции
finally:
    инструкции

Для демонстрации работы блока такого вида приведем пример из документации.

def divide(x, y):
    try:
        result = x / y
    except ZeroDivisionError:
        print("Попытка деления на ноль!")
    else:
        print(f"Результат деления {result}")
    finally:
        print("Выполнение блока 'finally'.")

Функция divide пытается поделить x на y.

Если деление происходит успешно (else), то выводится результат.

Результат деления 2.0
Выполнение блока 'finally'.

Если возникает ошибка деления на ноль, то она перехватывается и выводится соответствующее сообщение.

Попытка деления на ноль!
Выполнение блока 'finally'.

Если возникает ошибка другого рода, то её распространение продолжается.

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_13252/1759864827.py in <module>
----> 1 divide("2", "1")

~\AppData\Local\Temp/ipykernel_13252/3246861353.py in divide(x, y)
      1 def divide(x, y):
      2     try:
----> 3         result = x / y
      4     except ZeroDivisionError:
      5         print("division by zero!")

TypeError: unsupported operand type(s) for /: 'str' and 'str'

Блок finally выполняется в любом случае.

Уровень сложности
Средний

Время на прочтение
8 мин

Количество просмотров 14K

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

  • Что такое обработка исключений?

  • Разница между оператором if и обработкой исключений.

  • Использование разделов else и finally блока try-except для организации правильного обращения с ошибками.

  • Определение пользовательских исключений.

  • Рекомендации по обработке исключений.

Что такое обработка исключений?

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

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

Различия между оператором if и обработкой исключений

Главные различия между оператором if и обработкой исключений в Python произрастают из их целей и сценариев использования.

Оператор if — это базовый строительный элемент структурного программирования. Этот оператор проверяет условие и выполняет различные блоки кода, основываясь на том, истинно проверяемое условие или ложно. Вот пример:

temperature = int(input("Please enter temperature in Fahrenheit: "))
if temperature > 100:
    print("Hot weather alert! Temperature exceeded 100°F.")
elif temperature >= 70:
    print("Warm day ahead, enjoy sunny skies.")
else:
    print("Bundle up for chilly temperatures.")

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

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

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

# Определение функции, которая пытается поделить число на ноль
def divide(x, y):
    result = x / y
    return result
# Вызов функции divide с передачей ей x=5 и y=0
result = divide(5, 0)
print(f"Result of dividing {x} by {y}: {result}")

Вывод:

Traceback (most recent call last):
  File "<stdin>", line 8, in <module>
ZeroDivisionError: division by zero attempted

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

Вышеописанное исключение можно обработать, обернув вызов функции divide в блок try-except:

# Определение функции, которая пытается поделить число на ноль
def divide(x, y):
    result = x / y
    return result
# Вызов функции divide с передачей ей x=5 и y=0
try:
    result = divide(5, 0)
    print(f"Result of dividing {x} by {y}: {result}")
except ZeroDivisionError:
    print("Cannot divide by zero.")

Вывод:

Cannot divide by zero.

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

Подробности о других встроенных Python-исключениях можно найти здесь.

Использование разделов else и finally блока try-except для организации правильного обращения с ошибками

При работе с исключениями в Python рекомендуется включать в состав блоков try-except и раздел else, и раздел finally. Раздел else позволяет программисту настроить действия, производимые в том случае, если при выполнении кода, который защищают от проблем, не было вызвано исключений. А раздел finally позволяет обеспечить обязательное выполнение неких заключительных операций, вроде освобождения ресурсов, независимо от факта возникновения исключений (вот и вот — полезные материалы об этом).

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

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

try:
    # Открытие файла в режиме чтения
    file = open("file.txt", "r")
    print("Successful opened the file")
except FileNotFoundError:
    # Обработка ошибки, возникающей в том случае, если файл не найден
    print("File Not Found Error: No such file or directory")
    exit()
except PermissionError:
    # Обработка ошибок, связанных с разрешением на доступ к файлу
    print("Permission Denied Error: Access is denied")
else:
    # Всё хорошо - сделать что-то с данными, прочитанными из файла
    content = file.read().decode('utf-8')
    processed_data = process_content(content)
    
# Прибираемся после себя даже в том случае, если выше возникло исключение
finally:
    file.close()

В этом примере мы сначала пытаемся открыть файл file.txt для чтения (в подобной ситуации можно использовать выражение with, которое гарантирует правильное автоматическое закрытие объекта файла после завершения работы). Если в процессе выполнения операций файлового ввода/вывода возникают ошибки FileNotFoundError или PermissionError — выполняются соответствующие разделы except. Здесь, ради простоты, мы лишь выводим на экран сообщения об ошибках и выходим из программы в том случае, если файл не найден.

В противном случае, если в блоке try исключений не возникло, мы продолжаем работу, обрабатывая содержимое файла в ветви else. И наконец — выполняется «уборка» — файл закрывается независимо от возникновения исключения. Это обеспечивает блок finally (подробности смотрите здесь).

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

Определение пользовательских исключений

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

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

Вот пример определения пользовательского исключения, названного InvalidEmailAddress:

class InvalidEmailAddress(ValueError):
    def __init__(self, message):
        super().__init__(message)
        self.msgfmt = message

Это исключение является наследником ValueError. Его конструктор принимает необязательный аргумент message (по умолчанию он устанавливается в значение invalid email address).

Вызвать это исключение можно в том случае, если в программе встретился адрес электронной почты, имеющий некорректный формат:

def send_email(address):
    if isinstance(address, str) == False:
        raise InvalidEmailAddress("Invalid email address")
# Отправка электронного письма

Теперь, если функции send_email() будет передана строка, содержащая неправильно оформленный адрес, то, вместо сообщения стандартной ошибки TypeError, будет выдано настроенное заранее сообщение об ошибке, которое чётко указывает на возникшую проблему. Например, это может выглядеть так:

>>> send_email(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/to/project/main.py", line 8, in send_email
    raise InvalidEmailAddress("Invalid email address")
InvalidEmailAddress: Invalid email address

Рекомендации по обработке исключений

Вот несколько рекомендаций, относящихся к обработке ошибок в Python:

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

  2. Используйте содержательные сообщения об ошибках. Сделайте так, чтобы программа выводила бы, на экран, или в файл журнала, подробные сообщения об ошибках, которые помогут пользователям понять — что и почему пошло не так. Старайтесь не применять обобщённые сообщения об ошибках, наподобие Error occurred или Something bad happened. Вместо этого подумайте об удобстве пользователя и покажите сообщение, в котором будет дан совет по решению проблемы или будет приведена ссылка на документацию. Постарайтесь соблюсти баланс между выводом подробных сообщений и перегрузкой пользовательского интерфейса избыточными данными.

  3. Минимизируйте побочные эффекты. Постарайтесь свести к минимуму последствия сбойных операций, изолируя проблемные разделы кода посредством конструкции try-finally или try с использованием with. Сделайте так, чтобы после выполнения кода, было ли оно удачным или нет, обязательно выполнялись бы «очистительные» операции.

  4. Тщательно тестируйте код. Обеспечьте корректное поведение обработчиков ошибок в различных сценариях использования программы, подвергнув код всеобъемлющему тестированию.

  5. Регулярно выполняйте рефакторинг кода. Выполняйте рефакторинг фрагментов кода, подверженных ошибкам, чтобы улучшить их надёжность и производительность. Постарайтесь, чтобы ваша кодовая база была бы устроена по модульному принципу, чтобы её отдельные части слабо зависели бы друг от друга. Это позволяет независимым частям код самостоятельно эволюционировать, не оказывая негативного воздействия на другие его части.

  6. Логируйте важные события. Следите за интересными событиями своего приложения, записывая сведения о них в файл журнала или выводя в консоль. Это поможет вам выявлять проблемы на ранних стадиях их возникновения, не тратя время на длительный анализ большого количества неструктурированных логов.

Итоги

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

О, а приходите к нам работать? 🤗 💰

Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.

Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.

Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.

Присоединяйтесь к нашей команде.

In Python, all exceptions must be instances of a class that derives from
BaseException. In a try statement with an except
clause that mentions a particular class, that clause also handles any exception
classes derived from that class (but not exception classes from which it is
derived). Two exception classes that are not related via subclassing are never
equivalent, even if they have the same name.

The built-in exceptions listed below can be generated by the interpreter or
built-in functions. Except where mentioned, they have an “associated value”
indicating the detailed cause of the error. This may be a string or a tuple of
several items of information (e.g., an error code and a string explaining the
code). The associated value is usually passed as arguments to the exception
class’s constructor.

User code can raise built-in exceptions. This can be used to test an exception
handler or to report an error condition “just like” the situation in which the
interpreter raises the same exception; but beware that there is nothing to
prevent user code from raising an inappropriate error.

The built-in exception classes can be subclassed to define new exceptions;
programmers are encouraged to derive new exceptions from the Exception
class or one of its subclasses, and not from BaseException. More
information on defining exceptions is available in the Python Tutorial under
User-defined Exceptions.

When raising (or re-raising) an exception in an except or
finally clause
__context__ is automatically set to the last exception caught; if the
new exception is not handled the traceback that is eventually displayed will
include the originating exception(s) and the final exception.

When raising a new exception (rather than using a bare raise to re-raise
the exception currently being handled), the implicit exception context can be
supplemented with an explicit cause by using from with
raise:

raise new_exc from original_exc

The expression following from must be an exception or None. It
will be set as __cause__ on the raised exception. Setting
__cause__ also implicitly sets the __suppress_context__
attribute to True, so that using raise new_exc from None
effectively replaces the old exception with the new one for display
purposes (e.g. converting KeyError to AttributeError, while
leaving the old exception available in __context__ for introspection
when debugging.

The default traceback display code shows these chained exceptions in
addition to the traceback for the exception itself. An explicitly chained
exception in __cause__ is always shown when present. An implicitly
chained exception in __context__ is shown only if __cause__
is None and __suppress_context__ is false.

In either case, the exception itself is always shown after any chained
exceptions so that the final line of the traceback always shows the last
exception that was raised.

5.1. Base classes¶

The following exceptions are used mostly as base classes for other exceptions.

exception BaseException

The base class for all built-in exceptions. It is not meant to be directly
inherited by user-defined classes (for that, use Exception). If
str() is called on an instance of this class, the representation of
the argument(s) to the instance are returned, or the empty string when
there were no arguments.

args

The tuple of arguments given to the exception constructor. Some built-in
exceptions (like OSError) expect a certain number of arguments and
assign a special meaning to the elements of this tuple, while others are
usually called only with a single string giving an error message.

with_traceback(tb)

This method sets tb as the new traceback for the exception and returns
the exception object. It is usually used in exception handling code like
this:

try:
    ...
except SomeException:
    tb = sys.exc_info()[2]
    raise OtherException(...).with_traceback(tb)
exception Exception

All built-in, non-system-exiting exceptions are derived from this class. All
user-defined exceptions should also be derived from this class.

exception ArithmeticError

The base class for those built-in exceptions that are raised for various
arithmetic errors: OverflowError, ZeroDivisionError,
FloatingPointError.

exception BufferError

Raised when a buffer related operation cannot be
performed.

exception LookupError

The base class for the exceptions that are raised when a key or index used on
a mapping or sequence is invalid: IndexError, KeyError. This
can be raised directly by codecs.lookup().

5.2. Concrete exceptions¶

The following exceptions are the exceptions that are usually raised.

exception AssertionError

Raised when an assert statement fails.

exception AttributeError

Raised when an attribute reference (see Attribute references) or
assignment fails. (When an object does not support attribute references or
attribute assignments at all, TypeError is raised.)

exception EOFError

Raised when the input() function hits an end-of-file condition (EOF)
without reading any data. (N.B.: the io.IOBase.read() and
io.IOBase.readline() methods return an empty string when they hit EOF.)

exception FloatingPointError

Raised when a floating point operation fails. This exception is always defined,
but can only be raised when Python is configured with the
--with-fpectl option, or the WANT_SIGFPE_HANDLER symbol is
defined in the pyconfig.h file.

exception GeneratorExit

Raised when a generator or coroutine is closed;
see generator.close() and coroutine.close(). It
directly inherits from BaseException instead of Exception since
it is technically not an error.

exception ImportError

Raised when the import statement has troubles trying to
load a module. Also raised when the “from list” in from ... import
has a name that cannot be found.

The name and path attributes can be set using keyword-only
arguments to the constructor. When set they represent the name of the module
that was attempted to be imported and the path to any file which triggered
the exception, respectively.

Changed in version 3.3: Added the name and path attributes.

exception ModuleNotFoundError

A subclass of ImportError which is raised by import
when a module could not be located. It is also raised when None
is found in sys.modules.

New in version 3.6.

exception IndexError

Raised when a sequence subscript is out of range. (Slice indices are
silently truncated to fall in the allowed range; if an index is not an
integer, TypeError is raised.)

exception KeyError

Raised when a mapping (dictionary) key is not found in the set of existing keys.

exception KeyboardInterrupt

Raised when the user hits the interrupt key (normally Control-C or
Delete). During execution, a check for interrupts is made
regularly. The exception inherits from BaseException so as to not be
accidentally caught by code that catches Exception and thus prevent
the interpreter from exiting.

exception MemoryError

Raised when an operation runs out of memory but the situation may still be
rescued (by deleting some objects). The associated value is a string indicating
what kind of (internal) operation ran out of memory. Note that because of the
underlying memory management architecture (C’s malloc() function), the
interpreter may not always be able to completely recover from this situation; it
nevertheless raises an exception so that a stack traceback can be printed, in
case a run-away program was the cause.

exception NameError

Raised when a local or global name is not found. This applies only to
unqualified names. The associated value is an error message that includes the
name that could not be found.

exception NotImplementedError

This exception is derived from RuntimeError. In user defined base
classes, abstract methods should raise this exception when they require
derived classes to override the method, or while the class is being
developed to indicate that the real implementation still needs to be added.

Note

It should not be used to indicate that an operator or method is not
meant to be supported at all – in that case either leave the operator /
method undefined or, if a subclass, set it to None.

Note

NotImplementedError and NotImplemented are not interchangeable,
even though they have similar names and purposes. See
NotImplemented for details on when to use it.

exception OSError([arg])
exception OSError(errno, strerror[, filename[, winerror[, filename2]]])

This exception is raised when a system function returns a system-related
error, including I/O failures such as “file not found” or “disk full”
(not for illegal argument types or other incidental errors).

The second form of the constructor sets the corresponding attributes,
described below. The attributes default to None if not
specified. For backwards compatibility, if three arguments are passed,
the args attribute contains only a 2-tuple
of the first two constructor arguments.

The constructor often actually returns a subclass of OSError, as
described in OS exceptions below. The particular subclass depends on
the final errno value. This behaviour only occurs when
constructing OSError directly or via an alias, and is not
inherited when subclassing.

errno

A numeric error code from the C variable errno.

winerror

Under Windows, this gives you the native
Windows error code. The errno attribute is then an approximate
translation, in POSIX terms, of that native error code.

Under Windows, if the winerror constructor argument is an integer,
the errno attribute is determined from the Windows error code,
and the errno argument is ignored. On other platforms, the
winerror argument is ignored, and the winerror attribute
does not exist.

strerror

The corresponding error message, as provided by
the operating system. It is formatted by the C
functions perror() under POSIX, and FormatMessage()
under Windows.

filename
filename2

For exceptions that involve a file system path (such as open() or
os.unlink()), filename is the file name passed to the function.
For functions that involve two file system paths (such as
os.rename()), filename2 corresponds to the second
file name passed to the function.

Changed in version 3.3: EnvironmentError, IOError, WindowsError,
socket.error, select.error and
mmap.error have been merged into OSError, and the
constructor may return a subclass.

Changed in version 3.4: The filename attribute is now the original file name passed to
the function, instead of the name encoded to or decoded from the
filesystem encoding. Also, the filename2 constructor argument and
attribute was added.

exception OverflowError

Raised when the result of an arithmetic operation is too large to be
represented. This cannot occur for integers (which would rather raise
MemoryError than give up). However, for historical reasons,
OverflowError is sometimes raised for integers that are outside a required
range. Because of the lack of standardization of floating point exception
handling in C, most floating point operations are not checked.

exception RecursionError

This exception is derived from RuntimeError. It is raised when the
interpreter detects that the maximum recursion depth (see
sys.getrecursionlimit()) is exceeded.

New in version 3.5: Previously, a plain RuntimeError was raised.

exception ReferenceError

This exception is raised when a weak reference proxy, created by the
weakref.proxy() function, is used to access an attribute of the referent
after it has been garbage collected. For more information on weak references,
see the weakref module.

exception RuntimeError

Raised when an error is detected that doesn’t fall in any of the other
categories. The associated value is a string indicating what precisely went
wrong.

exception StopIteration

Raised by built-in function next() and an iterator‘s
__next__() method to signal that there are no further
items produced by the iterator.

The exception object has a single attribute value, which is
given as an argument when constructing the exception, and defaults
to None.

When a generator or coroutine function
returns, a new StopIteration instance is
raised, and the value returned by the function is used as the
value parameter to the constructor of the exception.

If a generator function defined in the presence of a from __future__
import generator_stop
directive raises StopIteration, it will be
converted into a RuntimeError (retaining the StopIteration
as the new exception’s cause).

Changed in version 3.3: Added value attribute and the ability for generator functions to
use it to return a value.

Changed in version 3.5: Introduced the RuntimeError transformation.

exception StopAsyncIteration

Must be raised by __anext__() method of an
asynchronous iterator object to stop the iteration.

New in version 3.5.

exception SyntaxError

Raised when the parser encounters a syntax error. This may occur in an
import statement, in a call to the built-in functions exec()
or eval(), or when reading the initial script or standard input
(also interactively).

Instances of this class have attributes filename, lineno,
offset and text for easier access to the details. str()
of the exception instance returns only the message.

exception IndentationError

Base class for syntax errors related to incorrect indentation. This is a
subclass of SyntaxError.

exception TabError

Raised when indentation contains an inconsistent use of tabs and spaces.
This is a subclass of IndentationError.

exception SystemError

Raised when the interpreter finds an internal error, but the situation does not
look so serious to cause it to abandon all hope. The associated value is a
string indicating what went wrong (in low-level terms).

You should report this to the author or maintainer of your Python interpreter.
Be sure to report the version of the Python interpreter (sys.version; it is
also printed at the start of an interactive Python session), the exact error
message (the exception’s associated value) and if possible the source of the
program that triggered the error.

exception SystemExit

This exception is raised by the sys.exit() function. It inherits from
BaseException instead of Exception so that it is not accidentally
caught by code that catches Exception. This allows the exception to
properly propagate up and cause the interpreter to exit. When it is not
handled, the Python interpreter exits; no stack traceback is printed. The
constructor accepts the same optional argument passed to sys.exit().
If the value is an integer, it specifies the system exit status (passed to
C’s exit() function); if it is None, the exit status is zero; if
it has another type (such as a string), the object’s value is printed and
the exit status is one.

A call to sys.exit() is translated into an exception so that clean-up
handlers (finally clauses of try statements) can be
executed, and so that a debugger can execute a script without running the risk
of losing control. The os._exit() function can be used if it is
absolutely positively necessary to exit immediately (for example, in the child
process after a call to os.fork()).

code

The exit status or error message that is passed to the constructor.
(Defaults to None.)

exception TypeError

Raised when an operation or function is applied to an object of inappropriate
type. The associated value is a string giving details about the type mismatch.

This exception may be raised by user code to indicate that an attempted
operation on an object is not supported, and is not meant to be. If an object
is meant to support a given operation but has not yet provided an
implementation, NotImplementedError is the proper exception to raise.

Passing arguments of the wrong type (e.g. passing a list when an
int is expected) should result in a TypeError, but passing
arguments with the wrong value (e.g. a number outside expected boundaries)
should result in a ValueError.

exception UnboundLocalError

Raised when a reference is made to a local variable in a function or method, but
no value has been bound to that variable. This is a subclass of
NameError.

exception UnicodeError

Raised when a Unicode-related encoding or decoding error occurs. It is a
subclass of ValueError.

UnicodeError has attributes that describe the encoding or decoding
error. For example, err.object[err.start:err.end] gives the particular
invalid input that the codec failed on.

encoding

The name of the encoding that raised the error.

reason

A string describing the specific codec error.

object

The object the codec was attempting to encode or decode.

start

The first index of invalid data in object.

end

The index after the last invalid data in object.

exception UnicodeEncodeError

Raised when a Unicode-related error occurs during encoding. It is a subclass of
UnicodeError.

exception UnicodeDecodeError

Raised when a Unicode-related error occurs during decoding. It is a subclass of
UnicodeError.

exception UnicodeTranslateError

Raised when a Unicode-related error occurs during translating. It is a subclass
of UnicodeError.

exception ValueError

Raised when a built-in operation or function receives an argument that has the
right type but an inappropriate value, and the situation is not described by a
more precise exception such as IndexError.

exception ZeroDivisionError

Raised when the second argument of a division or modulo operation is zero. The
associated value is a string indicating the type of the operands and the
operation.

The following exceptions are kept for compatibility with previous versions;
starting from Python 3.3, they are aliases of OSError.

exception EnvironmentError
exception IOError
exception WindowsError

Only available on Windows.

5.2.1. OS exceptions¶

The following exceptions are subclasses of OSError, they get raised
depending on the system error code.

exception BlockingIOError

Raised when an operation would block on an object (e.g. socket) set
for non-blocking operation.
Corresponds to errno EAGAIN, EALREADY,
EWOULDBLOCK and EINPROGRESS.

In addition to those of OSError, BlockingIOError can have
one more attribute:

characters_written

An integer containing the number of characters written to the stream
before it blocked. This attribute is available when using the
buffered I/O classes from the io module.

exception ChildProcessError

Raised when an operation on a child process failed.
Corresponds to errno ECHILD.

exception ConnectionError

A base class for connection-related issues.

Subclasses are BrokenPipeError, ConnectionAbortedError,
ConnectionRefusedError and ConnectionResetError.

exception BrokenPipeError

A subclass of ConnectionError, raised when trying to write on a
pipe while the other end has been closed, or trying to write on a socket
which has been shutdown for writing.
Corresponds to errno EPIPE and ESHUTDOWN.

exception ConnectionAbortedError

A subclass of ConnectionError, raised when a connection attempt
is aborted by the peer.
Corresponds to errno ECONNABORTED.

exception ConnectionRefusedError

A subclass of ConnectionError, raised when a connection attempt
is refused by the peer.
Corresponds to errno ECONNREFUSED.

exception ConnectionResetError

A subclass of ConnectionError, raised when a connection is
reset by the peer.
Corresponds to errno ECONNRESET.

exception FileExistsError

Raised when trying to create a file or directory which already exists.
Corresponds to errno EEXIST.

exception FileNotFoundError

Raised when a file or directory is requested but doesn’t exist.
Corresponds to errno ENOENT.

exception InterruptedError

Raised when a system call is interrupted by an incoming signal.
Corresponds to errno EINTR.

Changed in version 3.5: Python now retries system calls when a syscall is interrupted by a
signal, except if the signal handler raises an exception (see PEP 475
for the rationale), instead of raising InterruptedError.

exception IsADirectoryError

Raised when a file operation (such as os.remove()) is requested
on a directory.
Corresponds to errno EISDIR.

exception NotADirectoryError

Raised when a directory operation (such as os.listdir()) is requested
on something which is not a directory.
Corresponds to errno ENOTDIR.

exception PermissionError

Raised when trying to run an operation without the adequate access
rights — for example filesystem permissions.
Corresponds to errno EACCES and EPERM.

exception ProcessLookupError

Raised when a given process doesn’t exist.
Corresponds to errno ESRCH.

exception TimeoutError

Raised when a system function timed out at the system level.
Corresponds to errno ETIMEDOUT.

New in version 3.3: All the above OSError subclasses were added.

See also

PEP 3151 — Reworking the OS and IO exception hierarchy

5.3. Warnings¶

The following exceptions are used as warning categories; see the warnings
module for more information.

exception Warning

Base class for warning categories.

exception UserWarning

Base class for warnings generated by user code.

exception DeprecationWarning

Base class for warnings about deprecated features.

exception PendingDeprecationWarning

Base class for warnings about features which will be deprecated in the future.

exception SyntaxWarning

Base class for warnings about dubious syntax.

exception RuntimeWarning

Base class for warnings about dubious runtime behavior.

exception FutureWarning

Base class for warnings about constructs that will change semantically in the
future.

exception ImportWarning

Base class for warnings about probable mistakes in module imports.

exception UnicodeWarning

Base class for warnings related to Unicode.

exception BytesWarning

Base class for warnings related to bytes and bytearray.

exception ResourceWarning

Base class for warnings related to resource usage.

New in version 3.2.

5.4. Exception hierarchy¶

The class hierarchy for built-in exceptions is:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

Понравилась статья? Поделить с друзьями:
  • Общее правило квалификации при фактической ошибке
  • Объясните на основе какой синтаксической ошибки построен анекдот
  • Общие ошибки допускаемые дрессировщиком
  • Объясните и исправьте лексические ошибки
  • Объясни какие ошибки отметил учитель запиши слова правильно