Содержание:развернуть
- Как устроен механизм исключений
- Как обрабатывать исключения в 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
содержится следующую информацию (читаем снизу вверх):
TypeError
— тип ошибки (означает, что операция не может быть выполнена с переменной этого типа);can only concatenate str (not "int") to str
— подробное описание ошибки (конкатенировать можно только строку со строкой);- Стек вызова функций (1-я линия — место, 2-я линия — код). В нашем примере видно, что в файле «test.py» на 11-й линии был вызов функции «a» со строковым аргументом «10». Далее был вызов функции «b».
print(value + 1)
это последнее, что было выполнено — тут и произошла ошибка. 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
В программе может возникнуть несколько исключений, например:
- Ошибка преобразования введенных значений к типу
float
(«ValueError»); - Деление на ноль («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("Перехвачена ошибка деления на ноль!")
Перехвачена ошибка деления на ноль!
Видим, что
-
сообщения об ошибке с типом ошибки и трассировочной информацией не появилось, т.к. ошибка была обработана;
-
вместо этого выполнилась инструкция
print
в блокеexcept ZeroDivisionError
; -
инструкция
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:
-
Проектируйте код в расчёте на возможное возникновение ошибок. Заранее планируйте устройство кода с учётом возможных сбоев и проектируйте программы так, чтобы они могли бы достойно обрабатывать эти сбои. Это означает — предугадывать возможные пограничные случаи и реализовывать подходящие обработчики ошибок.
-
Используйте содержательные сообщения об ошибках. Сделайте так, чтобы программа выводила бы, на экран, или в файл журнала, подробные сообщения об ошибках, которые помогут пользователям понять — что и почему пошло не так. Старайтесь не применять обобщённые сообщения об ошибках, наподобие
Error occurred
илиSomething bad happened
. Вместо этого подумайте об удобстве пользователя и покажите сообщение, в котором будет дан совет по решению проблемы или будет приведена ссылка на документацию. Постарайтесь соблюсти баланс между выводом подробных сообщений и перегрузкой пользовательского интерфейса избыточными данными. -
Минимизируйте побочные эффекты. Постарайтесь свести к минимуму последствия сбойных операций, изолируя проблемные разделы кода посредством конструкции
try-finally
илиtry
с использованиемwith
. Сделайте так, чтобы после выполнения кода, было ли оно удачным или нет, обязательно выполнялись бы «очистительные» операции. -
Тщательно тестируйте код. Обеспечьте корректное поведение обработчиков ошибок в различных сценариях использования программы, подвергнув код всеобъемлющему тестированию.
-
Регулярно выполняйте рефакторинг кода. Выполняйте рефакторинг фрагментов кода, подверженных ошибкам, чтобы улучшить их надёжность и производительность. Постарайтесь, чтобы ваша кодовая база была бы устроена по модульному принципу, чтобы её отдельные части слабо зависели бы друг от друга. Это позволяет независимым частям код самостоятельно эволюционировать, не оказывая негативного воздействия на другие его части.
-
Логируйте важные события. Следите за интересными событиями своего приложения, записывая сведения о них в файл журнала или выводя в консоль. Это поможет вам выявлять проблемы на ранних стадиях их возникновения, не тратя время на длительный анализ большого количества неструктурированных логов.
Итоги
Написание кода обработки ошибок — это неотъемлемая часть индустрии разработки ПО, и, в частности — разработки на 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, useException
). 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 (likeOSError
) 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 bycodecs.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.: theio.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 theWANT_SIGFPE_HANDLER
symbol is
defined in thepyconfig.h
file.
-
exception
GeneratorExit
¶ -
Raised when a generator or coroutine is closed;
seegenerator.close()
andcoroutine.close()
. It
directly inherits fromBaseException
instead ofException
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” infrom ... import
has a name that cannot be found.The
name
andpath
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
andpath
attributes.
-
exception
ModuleNotFoundError
¶ -
A subclass of
ImportError
which is raised byimport
when a module could not be located. It is also raised whenNone
is found insys.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 fromBaseException
so as to not be
accidentally caught by code that catchesException
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’smalloc()
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 toNone
.Note
NotImplementedError
andNotImplemented
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 toNone
if not
specified. For backwards compatibility, if three arguments are passed,
theargs
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 finalerrno
value. This behaviour only occurs when
constructingOSError
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. Theerrno
attribute is then an approximate
translation, in POSIX terms, of that native error code.Under Windows, if the winerror constructor argument is an integer,
theerrno
attribute is determined from the Windows error code,
and the errno argument is ignored. On other platforms, the
winerror argument is ignored, and thewinerror
attribute
does not exist.
-
strerror
¶ -
The corresponding error message, as provided by
the operating system. It is formatted by the C
functionsperror()
under POSIX, andFormatMessage()
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 intoOSError
, 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 theweakref
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
toNone
.When a generator or coroutine function
returns, a newStopIteration
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__
directive raises
import generator_stopStopIteration
, it will be
converted into aRuntimeError
(retaining theStopIteration
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 functionsexec()
oreval()
, or when reading the initial script or standard input
(also interactively).Instances of this class have attributes
filename
,lineno
,
offset
andtext
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 ofSyntaxError
.
-
exception
TabError
¶ -
Raised when indentation contains an inconsistent use of tabs and spaces.
This is a subclass ofIndentationError
.
-
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 ofException
so that it is not accidentally
caught by code that catchesException
. 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 tosys.exit()
.
If the value is an integer, it specifies the system exit status (passed to
C’sexit()
function); if it isNone
, 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 oftry
statements) can be
executed, and so that a debugger can execute a script without running the risk
of losing control. Theos._exit()
function can be used if it is
absolutely positively necessary to exit immediately (for example, in the child
process after a call toos.fork()
).-
code
¶ -
The exit status or error message that is passed to the constructor.
(Defaults toNone
.)
-
-
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 aTypeError
, but passing
arguments with the wrong value (e.g. a number outside expected boundaries)
should result in aValueError
.
-
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 ofValueError
.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
ofUnicodeError
.
-
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 asIndexError
.
-
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 toerrno
EAGAIN
,EALREADY
,
EWOULDBLOCK
andEINPROGRESS
.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 theio
module.
-
-
exception
ChildProcessError
¶ -
Raised when an operation on a child process failed.
Corresponds toerrno
ECHILD
.
-
exception
ConnectionError
¶ -
A base class for connection-related issues.
Subclasses are
BrokenPipeError
,ConnectionAbortedError
,
ConnectionRefusedError
andConnectionResetError
.
-
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 toerrno
EPIPE
andESHUTDOWN
.
-
exception
ConnectionAbortedError
¶ -
A subclass of
ConnectionError
, raised when a connection attempt
is aborted by the peer.
Corresponds toerrno
ECONNABORTED
.
-
exception
ConnectionRefusedError
¶ -
A subclass of
ConnectionError
, raised when a connection attempt
is refused by the peer.
Corresponds toerrno
ECONNREFUSED
.
-
exception
ConnectionResetError
¶ -
A subclass of
ConnectionError
, raised when a connection is
reset by the peer.
Corresponds toerrno
ECONNRESET
.
-
exception
FileExistsError
¶ -
Raised when trying to create a file or directory which already exists.
Corresponds toerrno
EEXIST
.
-
exception
FileNotFoundError
¶ -
Raised when a file or directory is requested but doesn’t exist.
Corresponds toerrno
ENOENT
.
-
exception
InterruptedError
¶ -
Raised when a system call is interrupted by an incoming signal.
Corresponds toerrno
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 raisingInterruptedError
.
-
exception
IsADirectoryError
¶ -
Raised when a file operation (such as
os.remove()
) is requested
on a directory.
Corresponds toerrno
EISDIR
.
-
exception
NotADirectoryError
¶ -
Raised when a directory operation (such as
os.listdir()
) is requested
on something which is not a directory.
Corresponds toerrno
ENOTDIR
.
-
exception
PermissionError
¶ -
Raised when trying to run an operation without the adequate access
rights — for example filesystem permissions.
Corresponds toerrno
EACCES
andEPERM
.
-
exception
ProcessLookupError
¶ -
Raised when a given process doesn’t exist.
Corresponds toerrno
ESRCH
.
-
exception
TimeoutError
¶ -
Raised when a system function timed out at the system level.
Corresponds toerrno
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
andbytearray
.
-
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