Содержание:развернуть
- Как устроен механизм исключений
- Как обрабатывать исключения в 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 можно ознакомиться в официальной документации.
Уровень сложности
Средний
Время на прочтение
8 мин
Количество просмотров 6.1K
Люди, которые пишут код, часто воспринимают работу с исключениями как необходимое зло. Но освоение системы обработки исключений в 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 разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.
Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.
Присоединяйтесь к нашей команде.
How do I manually throw/raise an exception in Python?
Use the most specific Exception constructor that semantically fits your issue.
Be specific in your message, e.g.:
raise ValueError('A very specific bad thing happened.')
Don’t raise generic exceptions
Avoid raising a generic Exception
. To catch it, you’ll have to catch all other more specific exceptions that subclass it.
Problem 1: Hiding bugs
raise Exception('I know Python!') # Don't! If you catch, likely to hide bugs.
For example:
def demo_bad_catch():
try:
raise ValueError('Represents a hidden bug, do not catch this')
raise Exception('This is the exception you expect to handle')
except Exception as error:
print('Caught this error: ' + repr(error))
>>> demo_bad_catch()
Caught this error: ValueError('Represents a hidden bug, do not catch this',)
Problem 2: Won’t catch
And more specific catches won’t catch the general exception:
def demo_no_catch():
try:
raise Exception('general exceptions not caught by specific handling')
except ValueError as e:
print('we will not catch exception: Exception')
>>> demo_no_catch()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in demo_no_catch
Exception: general exceptions not caught by specific handling
Best Practices: raise
statement
Instead, use the most specific Exception constructor that semantically fits your issue.
raise ValueError('A very specific bad thing happened')
which also handily allows an arbitrary number of arguments to be passed to the constructor:
raise ValueError('A very specific bad thing happened', 'foo', 'bar', 'baz')
These arguments are accessed by the args
attribute on the Exception
object. For example:
try:
some_code_that_may_raise_our_value_error()
except ValueError as err:
print(err.args)
prints
('message', 'foo', 'bar', 'baz')
In Python 2.5, an actual message
attribute was added to BaseException
in favor of encouraging users to subclass Exceptions and stop using args
, but the introduction of message
and the original deprecation of args has been retracted.
Best Practices: except
clause
When inside an except clause, you might want to, for example, log that a specific type of error happened, and then re-raise. The best way to do this while preserving the stack trace is to use a bare raise statement. For example:
logger = logging.getLogger(__name__)
try:
do_something_in_app_that_breaks_easily()
except AppError as error:
logger.error(error)
raise # just this!
# raise AppError # Don't do this, you'll lose the stack trace!
Don’t modify your errors… but if you insist.
You can preserve the stacktrace (and error value) with sys.exc_info()
, but this is way more error prone and has compatibility problems between Python 2 and 3, prefer to use a bare raise
to re-raise.
To explain — the sys.exc_info()
returns the type, value, and traceback.
type, value, traceback = sys.exc_info()
This is the syntax in Python 2 — note this is not compatible with Python 3:
raise AppError, error, sys.exc_info()[2] # avoid this.
# Equivalently, as error *is* the second object:
raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]
If you want to, you can modify what happens with your new raise — e.g. setting new args
for the instance:
def error():
raise ValueError('oops!')
def catch_error_modify_message():
try:
error()
except ValueError:
error_type, error_instance, traceback = sys.exc_info()
error_instance.args = (error_instance.args[0] + ' <modification>',)
raise error_type, error_instance, traceback
And we have preserved the whole traceback while modifying the args. Note that this is not a best practice and it is invalid syntax in Python 3 (making keeping compatibility much harder to work around).
>>> catch_error_modify_message()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in catch_error_modify_message
File "<stdin>", line 2, in error
ValueError: oops! <modification>
In Python 3:
raise error.with_traceback(sys.exc_info()[2])
Again: avoid manually manipulating tracebacks. It’s less efficient and more error prone. And if you’re using threading and sys.exc_info
you may even get the wrong traceback (especially if you’re using exception handling for control flow — which I’d personally tend to avoid.)
Python 3, Exception chaining
In Python 3, you can chain Exceptions, which preserve tracebacks:
raise RuntimeError('specific message') from error
Be aware:
- this does allow changing the error type raised, and
- this is not compatible with Python 2.
Deprecated Methods:
These can easily hide and even get into production code. You want to raise an exception, and doing them will raise an exception, but not the one intended!
Valid in Python 2, but not in Python 3 is the following:
raise ValueError, 'message' # Don't do this, it's deprecated!
Only valid in much older versions of Python (2.4 and lower), you may still see people raising strings:
raise 'message' # really really wrong. don't do this.
In all modern versions, this will actually raise a TypeError
, because you’re not raising a BaseException
type. If you’re not checking for the right exception and don’t have a reviewer that’s aware of the issue, it could get into production.
Example Usage
I raise Exceptions to warn consumers of my API if they’re using it incorrectly:
def api_func(foo):
'''foo should be either 'baz' or 'bar'. returns something very useful.'''
if foo not in _ALLOWED_ARGS:
raise ValueError('{foo} wrong, use "baz" or "bar"'.format(foo=repr(foo)))
Create your own error types when apropos
«I want to make an error on purpose, so that it would go into the except»
You can create your own error types, if you want to indicate something specific is wrong with your application, just subclass the appropriate point in the exception hierarchy:
class MyAppLookupError(LookupError):
'''raise this when there's a lookup error for my app'''
and usage:
if important_key not in resource_dict and not ok_to_be_missing:
raise MyAppLookupError('resource is missing, and that is not ok.')
Введение | |
Пример с базовым Exception | |
Два исключения | |
except Error as e:: Печать текста ошибки | |
else | |
finally | |
raise | |
Пример 2 | |
Пример 3 | |
Исключения, которые не нужно обрабатывать | |
Список исключений | |
Разбор примеров: IndexError, ValueError, KeyError | |
Похожие статьи |
Введение
Если в коде есть ошибка, которую видит интерпретатор поднимается исключение, создается так называемый
Exception Object, выполнение останавливается, в терминале
показывается Traceback.
В английском языке используется словосочетание Raise Exception
Исключение, которое не было предусмотрено разработчиком называется необработанным (Unhandled Exception)
Такое поведение не всегда является оптимальным. Не все ошибки дожны останавливать работу кода.
Возможно, где-то разработчик ожидает появление ошибок и их можно обработать по-другому.
try и except нужны прежде всего для того, чтобы код правильно реагировал на возможные ошибки и продолжал выполняться
там, где появление ошибки некритично.
Исключение, которое предусмотрено в коде называется обработанным (Handled)
Блок try except имеет следующий синтаксис
try:
pass
except Exception:
pass
else:
pass
finally:
pass
В этой статье я создал файл
try_except.py
куда копирую код из примеров.
Пример
Попробуем открыть несуществующий файл и воспользоваться базовым Exception
try:
f = open(‘missing.txt’)
except Exception:
print(‘ERR: File not found’)
python try_except.py
ERR: No missing.txt file found
Ошибка поймана, видно наше сообщение а не Traceback
Проверим, что когда файл существует всё хорошо
try:
f = open(‘existing.txt’)
except Exception:
print(‘ERR: File not found’)
python try_except.py
Пустота означает успех
Два исключения
Если ошибок больше одной нужны дополнительные исключения. Попробуем открыть существующий файл, и после этого
добавить ошибку.
try:
f = open(‘existing.txt’)
x = bad_value
except Exception:
print(‘ERR: File not found’)
python try_except.py
ERR: File not found
Файл открылся, но так как в следующей строке ошибка — в терминале появилось вводящее в заблуждение сообщение.
Проблема не в том, что «File not found» а в том, что bad_value нигде не определёно.
Избежать сбивающих с толку сообщений можно указав тип ожидаемой ошибки. В данном примере это FileNotFoundError
try:
# expected exception
f = open(‘existing.txt’)
# unexpected exception should result in Traceback
x = bad_value
except FileNotFoundError:
print(‘ERR: File not found’)
python try_except.py
Traceback (most recent call last):
File «/home/andrei/python/try_except2.py», line 5, in <module>
x = bad_value
NameError: name ‘bad_value’ is not defined
Вторая ошибка не поймана поэтому показан Traceback
Поймать обе ошибки можно добавив второй Exception
try:
# expected exception should be caught by FileNotFoundError
f = open(‘missing.txt’)
# unexpected exception should be caught by Exception
x = bad_value
except FileNotFoundError:
print(‘ERR: File not found’)
except Exception:
print(‘ERR: Something unexpected went wrong’)
python try_except.py
ERR: File not found
ERR: Something unexpected went wrong
Печать текста ошибки
Вместо своего текста можно выводить текст ошибки. Попробуем с существующим файлом — должна быть одна пойманная ошибка.
try:
# expected exception should be caught by FileNotFoundError
f = open(‘existing.txt’)
# unexpected exception should be caught by Exception
x = bad_value
except FileNotFoundError as e:
print(e)
except Exception as e:
print(e)
python try_except.py
name ‘bad_value’ is not defined
Теперь попытаемся открыть несуществующий файл — должно быть две пойманные ошибки.
try:
# expected exception should be caught by FileNotFoundError
f = open(‘missing.txt’)
# unexpected exception should be caught by Exception
x = bad_value
except FileNotFoundError as e:
print(e)
except Exception as e:
print(e)
python try_except.py
name ‘bad_value’ is not defined
[Errno 2] No such file or directory: ‘missing.txt’
else
Блок else будет выполнен если исключений не будет поймано.
Попробуем открыть существующий файл
existing.txt
в котором есть строка
www.heihei.ru
try:
f = open(‘existing.txt’)
except FileNotFoundError as e:
print(e)
except Exception as e:
print(e)
else:
print(f.read())
f.close()
python try_except.py
www.heihei.ru
Если попробовать открыть несуществующий файл
missing.txt
то исключение обрабатывается, а код из блока else не выполняется.
[Errno 2] No such file or directory: ‘missing.txt’
finally
Блок finally будет выполнен независимо от того, поймано исключение или нет
try:
f = open(‘existing.txt’)
except FileNotFoundError as e:
print(e)
except Exception as e:
print(e)
else:
print(f.read())
f.close()
finally:
print(«Finally!»)
www.heihei.ru
Finally!
А если попытаться открыть несуществующий
missing.txt
[Errno 2] No such file or directory: ‘missing.txt’
Finally!
Когда нужно применять finally:
Рассмотрим скрипт, который вносит какие-то изменения в систему.
Затем он пытается что-то сделать. В конце возвращает
систему в исходное состояние.
Если ошибка случится в середине скрипта — он уже не сможет вернуть систему в исходное состояние.
Но если вынести возврат к исходному состоянию в блок finally он сработает даже при ошибке
в предыдущем блоке.
import os
def make_at(path, dir_name):
original_path = os.getcwd()
os.chdir(path)
os.mkdir(dir_name)
os.chdir(original_path)
Этот скрипт не вернётся в исходную директорию при ошибке в os.mkdir(dir_name)
А у скрипта ниже такой проблемы нет
def make_at(path, dir_name):
original_path = os.getcwd()
os.chdir(path)
try:
os.mkdir(dir_name)
finally:
os.chdir(original_path)
Не лишнима будет добавить обработку и вывод исключения
import os
import sys
def make_at(path, dir_name):
original_path = os.getcwd()
os.chdir(path)
try:
os.mkdir(dir_name)
except OSError as e:
print(e, file=sys.stderr)
raise
finally:
os.chdir(original_path)
По умолчанию print() выводит в sys.stdout, но в случае ислючений логичнее выводить в sys.stderr
raise
Можно вызывать исключения вручную в любом месте кода с помощью
raise.
try:
f = open(‘outdated.txt’)
if f.name == ‘outdated.txt’:
raise Exception
except FileNotFoundError as e:
print(e)
except Exception as e:
print(‘File is outdated!’)
else:
print(f.read())
f.close()
finally:
print(«Finally!»)
python try_except.py
File is outdated!
Finally!
raise
можно использовать для перевызова исключения, например, чтобы уйти от использования кодов ошибок.
Для этого достаточно вызвать raise без аргументов — поднимется текущее исключение.
Пример 2
Рассмотрим функцию, которая принимает числа прописью и возвращает цифрами
DIGIT_MAP = {
‘zero’: ‘0’,
‘one’: ‘1’,
‘two’: ‘2’,
‘three’: ‘3’,
‘four’: ‘4’,
‘five’: ‘5’,
‘six’: ‘6’,
‘seven’: ‘7’,
‘eight’: ‘8’,
‘nine’: ‘9’,
}
def convert(s):
number = »
for token in s:
number += DIGIT_MAP[token]
x = int(number)
return x
python
>>> from exc1 import convert
>>> convert(«one three three seven».split())
1337
Теперь передадим аргумент, который не предусмотрен в словаре
>>> convert(«something unseen«.split())
Traceback (most recent call last):
File «<stdin>», line 1, in <module>
File «/home/andrei/python/exc1.py», line 17, in convert
number &plu= DIGIT_MAP[token]
KeyError: ‘something’
KeyError — это тип Exception объекта. Полный список можно изучить в конце статьи.
Исключение прошло следующий путь:
REPL → convert() → DIGIT_MAP(«something») → KeyError
Обработать это исключение можно внеся изменения в функцию convert
convert(s):
try:
number = »
for token in s:
number += DIGIT_MAP[token]
x = int(number)
print(«Conversion succeeded! x = «, x)
except KeyError:
print(«Conversion failed!»)
x = —1
return x
>>> from exc1 import convert
>>> convert(«one nine six one».split())
Conversion succeeded! x = 1961
1961
>>> convert(«something unseen».split())
Conversion failed!
-1
Эта обработка не спасает если передать int вместо итерируемого объекта
>>> convert(2022)
Traceback (most recent call last):
File «<stdin>», line 1, in <module>
File «/home/andrei/python/exc1.py», line 17, in convert
for token in s:
TypeError: ‘int’ object is not iterable
Нужно добавить обработку TypeError
…
except KeyError:
print(«Conversion failed!»)
x = —1
except TypeError:
print(«Conversion failed!»)
x = —1
return x
>>> from exc1 import convert
>>> convert(«2022».split())
Conversion failed!
-1
Избавимся от повторов, удалив принты, объединив два исключения в кортеж и вынесем присваивание значения x
из try блока.
Также добавим
докстринг
с описанием функции.
def convert(s):
«»»Convert a string to an integer.»»»
x = —1
try:
number = »
for token in s:
number += DIGIT_MAP[token]
x = int(number)
except (KeyError, TypeError):
pass
return x
>>> from exc4 import convert
>>> convert(«one nine six one».split())
1961
>>> convert(«bad nine six one».split())
-1
>>> convert(2022)
-1
Ошибки обрабатываются, но без принтов, процесс не очень информативен.
Грамотно показать текст сообщений об ошибках можно импортировав sys и изменив функцию
import sys
DIGIT_MAP = {
‘zero’: ‘0’,
‘one’: ‘1’,
‘two’: ‘2’,
‘three’: ‘3’,
‘four’: ‘4’,
‘five’: ‘5’,
‘six’: ‘6’,
‘seven’: ‘7’,
‘eight’: ‘8’,
‘nine’: ‘9’,
}
def convert(s):
«»»Convert a string to an integer.»»»
try:
number = »
for token in s:
number += DIGIT_MAP[token]
return(int(number))
except (KeyError, TypeError) as e:
print(f«Conversion error: {e!r}», file=sys.stderr)
return —1
>>> from exc1 import convert
>>> convert(2022)
Conversion error: TypeError(«‘int’ object is not iterable»)
-1
>>> convert(«one nine six one».split())
1961
>>> convert(«bad nine six one».split())
Conversion error: KeyError(‘bad’)
Ошибки обрабатываются и их текст виден в терминале.
С помощью
!r
выводится
repr()
ошибки
raise вместо кода ошибки
В предыдущем примере мы полагались на возвращение числа -1 в качестве кода ошибки.
Добавим к коду примера функцию string_log() и поработаем с ней
def string_log(s):
v = convert(s)
return log(v)
>>> from exc1 import string_log
>>> string_log(«one two eight».split())
4.852030263919617
>>> string_log(«bad one two».split())
Conversion error: KeyError(‘bad’)
Traceback (most recent call last):
File «<stdin>», line 1, in <module>
File «/home/andrei/exc1.py», line 32, in string_log
return log(v)
ValueError: math domain error
convert() вернул -1 а string_log попробовал его обработать и не смог.
Можно заменить return -1 на raise. Это считается более правильным подходом в Python
def convert(s):
«»»Convert a string to an integer.»»»
try:
number = »
for token in s:
number += DIGIT_MAP[token]
return(int(number))
except (KeyError, TypeError) as e:
print(f«Conversion error: {e!r}», file=sys.stderr)
raise
>>> from exc7 import string_log
>>> string_log(«one zero».split())
2.302585092994046
>>> string_log(«bad one two».split())
Conversion error: KeyError(‘bad’)
Traceback (most recent call last):
File «<stdin>», line 1, in <module>
File «/home/andrei/exc7.py», line 31, in string_log
v = convert(s)
File «/home/andrei/exc7.py», line 23, in convert
number += DIGIT_MAP[token]
KeyError: ‘bad’
Пример 3
Рассмотрим алгоритм по поиску квадратного корня
def sqrt(x):
«»»Compute square roots using the method
of Heron of Alexandria.
Args:
x: The number for which the square root
is to be computed.
Returns:
The square root of x.
«»»
guess = x
i = 0
while guess * guess != x and i < 20:
guess = (guess + x / guess) / 2.0
i += 1
return guess
def main():
print(sqrt(9))
print(sqrt(2))
if __name__ == ‘__main__’:
main()
python sqrt_ex.py
3.0
1.414213562373095
При попытке вычислить корень от -1 получим ошибку
def main():
print(sqrt(9))
print(sqrt(2))
print(sqrt(-1))
python sqrt_ex.py
3.0
1.414213562373095
Traceback (most recent call last):
File «/home/andrei/sqrt_ex.py», line 26, in <module>
main()
File «/home/andrei/sqrt_ex.py», line 23, in main
print(sqrt(-1))
File «/home/andrei/sqrt_ex.py», line 16, in sqrt
guess = (guess + x / guess) / 2.0
ZeroDivisionError: float division by zero
В строке
guess = (guess + x / guess) / 2.0
Происходит деление на ноль
Обработать можно следующим образом:
def main():
try:
print(sqrt(9))
print(sqrt(2))
print(sqrt(-1))
except ZeroDivisionError:
print(«Cannot compute square root «
«of a negative number.»)
print(«Program execution continues «
«normally here.»)
Обратите внимание на то, что в try помещены все вызовы функции
python sqrt_ex.py
3.0
1.414213562373095
Cannot compute square root of a negative number.
Program execution continues normally here.
Если пытаться делить на ноль несколько раз — поднимется одно исключение и всё что находится в блоке
try после выполняться не будет
def main():
try:
print(sqrt(9))
print(sqrt(-1))
print(sqrt(2))
print(sqrt(-1))
python sqrt_ex.py
3.0
Cannot compute square root of a negative number.
Program execution continues normally here.
Каждую попытку вычислить корень из -1 придётся обрабатывать отдельно. Это кажется неудобным, но
в этом и заключается смысл — каждое место где вы ждёте ислючение нужно помещать в свой
try except блок.
Можно обработать исключение так:
try:
while guess * guess != x and i < 20:
guess = (guess + x / guess) / 2.0
i += 1
except ZeroDivisionError:
raise ValueError()
return guess
def main():
print(sqrt(9))
print(sqrt(-1))
python sqrt_ex.py
3.0
Traceback (most recent call last):
File «/home/andrei/sqrt_ex3.py», line 17, in sqrt
guess = (guess + x / guess) / 2.0
ZeroDivisionError: float division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File «/home/andrei/sqrt_ex3.py», line 30, in <module>
main()
File «/home/andrei/sqrt_ex3.py», line 25, in main
print(sqrt(-1))
File «/home/andrei/sqrt_ex3.py», line 20, in sqrt
raise ValueError()
ValueError
Гораздо логичнее поднимать исключение сразу при получении аргумента
def sqrt(x):
«»»Compute square roots using the method
of Heron of Alexandria.
Args:
x: The number for which the square root
is to be computed.
Returns:
The square root of x.
Raises:
ValueError: If x is negative
«»»
if x < 0:
raise ValueError(
«Cannot compute square root of «
f«negative number {x}»)
guess = x
i = 0
while guess * guess != x and i < 20:
guess = (guess + x / guess) / 2.0
i += 1
return guess
def main():
print(sqrt(9))
print(sqrt(-1))
print(sqrt(2))
print(sqrt(-1))
if __name__ == ‘__main__’:
main()
python sqrt_ex.py
3.0
Traceback (most recent call last):
File «/home/avorotyn/python/lessons/pluralsight/core_python_getting_started/chapter8/sqrt_ex4.py», line 35, in <module>
main()
File «/home/avorotyn/python/lessons/pluralsight/core_python_getting_started/chapter8/sqrt_ex4.py», line 30, in main
print(sqrt(-1))
File «/home/avorotyn/python/lessons/pluralsight/core_python_getting_started/chapter8/sqrt_ex4.py», line 17, in sqrt
raise ValueError(
ValueError: Cannot compute square root of negative number -1
Пока получилось не очень — виден Traceback
Убрать Traceback можно добавив обработку ValueError в вызов функций
import sys
def sqrt(x):
«»»Compute square roots using the method
of Heron of Alexandria.
Args:
x: The number for which the square root
is to be computed.
Returns:
The square root of x.
Raises:
ValueError: If x is negative
«»»
if x < 0:
raise ValueError(
«Cannot compute square root of «
f«negative number {x}»)
guess = x
i = 0
while guess * guess != x and i < 20:
guess = (guess + x / guess) / 2.0
i += 1
return guess
def main():
try:
print(sqrt(9))
print(sqrt(2))
print(sqrt(-1))
print(«This is never printed»)
except ValueError as e:
print(e, file=sys.stderr)
print(«Program execution continues normally here.»)
if __name__ == ‘__main__’:
main()
python sqrt_ex.py
3.0
1.414213562373095
Cannot compute square root of negative number -1
Program execution continues normally here.
Исключения, которые не нужно обрабатывать
IndentationError, SyntaxError, NameError нужно исправлять в коде а не пытаться обработать.
Важно помнить, что использовать обработку исключений для замалчивания ошибок программиста недопустимо.
Список исключений
Список встроенных в Python исключений
Существуют следующие типы объектов Exception
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
IndexError
Объекты, которые поддерживают
протокол
Sequence должны поднимать исключение IndexError при использовании несуществующего индекса.
IndexError как и
KeyError
относится к ошибкам поиска LookupError
Пример
>>> a = [0, 1, 2]
>>> a[3]
Traceback (most recent call last):
File «<stdin>», line 1, in <module>
IndexError: list index out of range
ValueError
ValueError поднимается когда объект правильного типа, но содержит неправильное значение
>>> int(«text»)
Traceback (most recent call last):
File «<stdin>», line 1, in <module>
ValueError: invalid literal for int() with base 10: ‘text’
KeyError
KeyError поднимается когда поиск по ключам не даёт результата
>>> sites = dict(urn=1, heihei=2, eth1=3)
>>> sites[«topbicycle»]
Traceback (most recent call last):
File «<stdin>», line 1, in <module>
KeyError: ‘topbicycle’
TypeError
TypeError поднимается когда для успешного выполнения операции нужен объект
определённого типа, а предоставлен другой тип.
pi = 3.1415
text = «Pi is approximately « + pi
python str_ex.py
Traceback (most recent call last):
File «str_ex.py», line 3, in <module>
text = «Pi is approximately » + pi
TypeError: can only concatenate str (not «float») to str
Пример из статьи
str()
SyntaxError
SyntaxError поднимается когда допущена ошибка в синтаксисе языка, например, использован
несуществующий оператор.
Python 3.8.10 (default, Nov 14 2022, 12:59:47)
[GCC 9.4.0] on linux
Type «help», «copyright», «credits» or «license» for more information.
>>>
>>>
>>> 0 <> 0
File «<stdin>», line 1
0 <> 0
^
SyntaxError: invalid syntax
Пример из статьи
__future__
Python | |
Интерактивный режим | |
str: строки | |
: перенос строки | |
Списки [] | |
if, elif, else | |
Циклы | |
Функции | |
Пакеты | |
*args **kwargs | |
ООП | |
enum | |
Опеределить тип переменной Python | |
Тестирование с помощью Python | |
Работа с REST API на Python | |
Файлы: записать, прочитать, дописать, контекстный менеджер… | |
Скачать файл по сети | |
SQLite3: работа с БД | |
datetime: Дата и время в Python | |
json.dumps | |
Selenium + Python | |
Сложности при работе с Python | |
DJANGO | |
Flask | |
Скрипт для ZPL принтера | |
socket :Python Sockets | |
Виртуальное окружение | |
subprocess: выполнение bash команд из Python | |
multiprocessing: несколько процессов одновременно | |
psutil: cистемные ресурсы | |
sys.argv: аргументы командной строки | |
PyCharm: IDE | |
pydantic: валидация данных | |
paramiko: SSH из Python | |
enumerate | |
logging: запись в лог | |
Обучение программированию на Python |
Raise, return, and how to never fail silently in Python.
I hear this question a lot: “Do I raise or return this error in Python?”
The right answer will depend on the goals of your application logic. You want to ensure your Python code doesn’t fail silently, saving you and your teammates from having to hunt down deeply entrenched errors.
Here’s the difference between raise
and return
when handling failures in Python.
When to raise
The
raise
statement allows the programmer to force a specific exception to occur. (8.4 Raising Exceptions)
Use raise
when you know you want a specific behavior, such as:
raise TypeError("Wanted strawberry, got grape.")
Raising an exception terminates the flow of your program, allowing the exception to bubble up the call stack. In the above example, this would let you explicitly handle TypeError
later. If TypeError
goes unhandled, code execution stops and you’ll get an unhandled exception message.
Raise is useful in cases where you want to define a certain behavior to occur. For example, you may choose to disallow certain words in a text field:
if "raisins" in text_field:
raise ValueError("That word is not allowed here")
Raise takes an instance of an exception, or a derivative of the Exception class. Here are all of Python’s built-in exceptions.
Raise can help you avoid writing functions that fail silently. For example, this code will not raise an exception if JAM
doesn’t exist:
import os
def sandwich_or_bust(bread: str) -> str:
jam = os.getenv("JAM")
return bread + str(jam) + bread
s = sandwich_or_bust("U0001F35E")
print(s)
# Prints "🍞None🍞" which is not very tasty.
To cause the sandwich_or_bust()
function to actually bust, add a raise
:
import os
def sandwich_or_bust(bread: str) -> str:
jam = os.getenv("JAM")
if not jam:
raise ValueError("There is no jam. Sad bread.")
return bread + str(jam) + bread
s = sandwich_or_bust("U0001F35E")
print(s)
# ValueError: There is no jam. Sad bread.
Any time your code interacts with an external variable, module, or service, there is a possibility of failure. You can use raise
in an if
statement to help ensure those failures aren’t silent.
Raise in try
and except
To handle a possible failure by taking an action if there is one, use a try
… except
statement.
try:
s = sandwich_or_bust("U0001F35E")
print(s)
except ValueError:
buy_more_jam()
raise
This lets you buy_more_jam()
before re-raising the exception. If you want to propagate a caught exception, use raise
without arguments to avoid possible loss of the stack trace.
If you don’t know that the exception will be a ValueError
, you can also use a bare except:
or catch any derivative of the Exception
class with except Exception:
. Whenever possible, it’s better to raise and handle exceptions explicitly.
Use else
for code to execute if the try
does not raise an exception. For example:
try:
s = sandwich_or_bust("U0001F35E")
print(s)
except ValueError:
buy_more_jam()
raise
else:
print("Congratulations on your sandwich.")
You could also place the print line within the try
block, however, this is less explicit.
When to return
When you use return
in Python, you’re giving back a value. A function returns to the location it was called from.
While it’s more idiomatic to raise
errors in Python, there may be occasions where you find return
to be more applicable.
For example, if your Python code is interacting with other components that do not handle exception classes, you may want to return a message instead. Here’s an example using a try
… except
statement:
from typing import Union
def share_sandwich(sandwich: int) -> Union[float, Exception]:
try:
bad_math = sandwich / 0
return bad_math
except Exception as e:
return e
s = share_sandwich(1)
print(s)
# Prints "division by zero"
Note that when you return an Exception
class object, you’ll get a representation of its associated value, usually the first item in its list of arguments. In the example above, this is the string explanation of the exception. In some cases, it may be a tuple with other information about the exception.
You may also use return
to give a specific error object, such as with HttpResponseNotFound
in Django. For example, you may want to return a 404
instead of a 403
for security reasons:
if object.owner != request.user:
return HttpResponseNotFound
Using return
can help you write appropriately noisy code when your function is expected to give back a certain value, and when interacting with outside elements.
The most important part
Silent failures create some of the most frustrating bugs to find and fix. You can help create a pleasant development experience for yourself and your team by using raise
and return
to ensure that errors are handled in your Python code.
I write about good development practices and how to improve productivity as a software developer. You can get these tips right in your inbox by signing up below!