Уровень сложности
Средний
Время на прочтение
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 разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.
Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров.
Присоединяйтесь к нашей команде.
Содержание:развернуть
- Как устроен механизм исключений
- Как обрабатывать исключения в 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 возбуждаются автоматически, когда программный код допускает ошибку, а также могут возбуждаться и перехватываться самим программным кодом. Обрабатываются исключения четырьмя инструкциями.
try/except — перехватывает исключения, возбужденные интерпретатором или вашим программным кодом, и выполняет восстановительные операции.
try/finally выполняет заключительные операции независимо от того, возникло исключение или нет.
raise — дает возможность возбудить исключение программно.
assert — дает возможность возбудить исключение программно, при выполнении определенного условия.
Благодаря исключениям программа может перейти к обработчику исключения за один шаг, отменив все вызовы функций. Обработчик исключений (инструкция try
) ставит метку и выполняет некоторый программный код. Если затем где-нибудь в программе возникает исключение, интерпретатор немедленно возвращается к метке, отменяя все активные вызовы функций, которые были произведены после установки метки.
Назначение исключений
-
Обработка ошибок. Интерпретатор возбуждает исключение всякий раз, когда обнаруживает ошибку во время выполнения программы. Программа может перехватывать такие ошибки и обрабатывать их или просто игнорировать. Если ошибка игнорируется, интерпретатор выполняет действия, предусмотренные по умолчанию, – он останавливает выполнение программы и выводит сообщение об ошибке. Если такое поведение по умолчанию является нежелательным, можно добавить инструкцию try, которая позволит перехватывать обнаруженные ошибки и продолжить выполнение программы после инструкции try.
-
Уведомления о событиях Исключения могут также использоваться для уведомления о наступлении некоторых условий, что устраняет необходимость передавать куда-либо флаги результата или явно проверять их. Например, функция поиска может возбуждать исключение в случае неудачи, вместо того чтобы возвращать целочисленный признак в виде результата (и надеяться, что этот признак всегда будет интерпретироваться правильно).
-
Обработка особых ситуаций. Некоторые условия могут наступать так редко, что было бы слишком расточительно предусматривать проверку наступления таких условий с целью их обработки. Нередко такие проверки необычных ситуаций можно заменить обработчиками исключений.
-
Заключительные операции. Как будет показано далее, инструкция try/finally позволяет гарантировать выполнение завершающих операций независимо от наличия исключений.
-
Необычное управление потоком выполнения. И, наконец, так как исключения – это своего рода оператор «goto», их можно использовать как основу для экзотического управления потоком выполнения программы.
Примеры исключений
Предположим, что у нас имеется следующая функция:
def fetcher(obj, index): return obj[index]
s = "some_string" fetcher(s, 3)
Поскольку наш программный код не перехватывает это исключение явно, оно возвращает выполнение на верхний уровень программы и вызывает обработчик исключений по умолчанию, который просто выводит стандартное сообщение об ошибке. К настоящему моменту вы наверняка видели в своих программах подобные сообщения об ошибках. Они включают тип исключения, а также диагностическую информацию – список строк и функций, которые были активны в момент появления исключения.
IndexError: string index out of range
Иногда это совсем не то, что нам требуется. Например, серверные программы обычно должны оставаться активными даже после появления внутренних ошибок.
Если вам требуется избежать реакции на исключение по умолчанию, достаточно просто перехватить исключение, обернув вызов функции инструкцией try
:
try: fetcher(s, 20) except IndexError: print("got exception") print("continuing")
Finally
Инструкции try
могут включать блоки finally
. Эти блоки выглядят точно так же, как обработчики except
. Комбинация try/finally
определяет завершающие действия, которые всегда выполняются «на выходе», независимо от того, возникло исключение в блоке try
или нет.
На практике комбинацию try/except
удобно использовать для перехвата и восстановления после исключений, а комбинацию try/finally
– в случаях, когда необходимо гарантировать выполнение заключительных действий независимо от того, возникло исключение в блоке try
или нет. Например, комбинацию try/except
можно было бы использовать для перехвата ошибок, возникающих в импортированной библиотеке, созданной сторонним разработчиком, а комбинацию try/finally
– чтобы гарантировать закрытие файлов и соединений с сервером.
try/finally
можно использовать вместо with/as
.
try: f = open("some_file.txt", "w") except IOError as err: print("Получаем ошибку и печатаем её: ", err) finally: f.close() print("Этот код выполнится")
Типы исключений
В Python есть два больших типа исключений. Первый — это исключения из стандартной библиотеки в Python, второй тип исключений — это пользовательские исключения. Они могут быть сгенерированы и обработаны самим программистом при написании программ на Python. Давайте посмотрим на иерархию исключений в стандартной библиотеке Python. Все исключения наследуются от базового класса BaseException
:
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- AssertionError
+-- AttributeError
+-- LookupError
+-- IndexError
+-- KeyError
+-- OSError
+-- SystemError
+-- TypeError
+-- ValueError
Существуют несколько системных исключений, например, SystemExit
(генерируется, если мы вызвали функцию OSExit
), KeyboardInterrupt
(генерируется, если мы нажали сочетание клавиш Ctrl + C
) и так далее. Все остальные исключения генерируется от базового класса Exception
. Именно от этого класса нужно порождать свои исключения.
Попробуем преобразовать строку в целое число, видим ValueError
:
ValueError: invalid literal for int() with base 10: ‘asdf’
Получим TypeError
при попытке сложить целое число со строкой:
TypeError: unsupported operand type(s) for +: ‘int’ and ‘str’
Чтобы отлавливать любые ошибки программы, возникающеие в коде, можно отлавливать ошибку Exception
try: """ some code """ except Exception: print("got all exceptions") print("continuing")
Внимание! Не стоит отливливать BaseException
или KeyboardInterrupt
, т.к. иногда вы даже не сможете выйти из программы. Python добускает возможность не указывать тип ошибки после except
, что равнозначно except BaseException
.
while True: try: raw = input("введите число: ") number = int(raw) break except: print("некорректное значение")
raise
Исключения можно вызывать самостоятельно при помощи ключевого слова raise
.
try: raw = input("введите число: ") if not raw.isdigit(): raise ValueError except ValueError: print("некорректное значение!")
введите число: в
некорректное значение!
AssertionError
Говоря об исключениях, нельзя не затронуть инструкцию assert
. По умолчанию, если выполнить инструкцию assert
с логическим выражением, результат которого равен True
, ничего не произойдет. Но если попробовать выполнить инструкцию assert
с логическим выражением, которое равно False
, то будет сгенерировано исключение AssertionError
. Также мы можем передать дополнительную строку в сам объект AssertionError
.
user_input = input("Введите ваше имя: ") assert user_input, "Пустая строка!" print("Выполнится только если введено")
AssertionError: Пустая строка!
Исключения AssertionError
предназначены скорее для программистов. При написании наших программ на этапе разработки мы должны видеть, что делаем что-то не так (например, передали в функцию некорректное значение). Не нужно, например, обрабатывать пользовательский ввод и пытаться обработать исключение AssertionError
блоком try except
. Если таких мест будет очень много, то это затронет и производительность нашей программы.
Однако, есть возможность отключить все инструкции assert при помощи флага −O
. Тогда AssertionError
не будет сгенерирована. Этим и отличаются исключения AssertionError
от обычных пользовательских исключений и исключений стандартной библиотеки.
Пользовательские исключения
Можно определять пользовательские исключения, чем часто пользуются различные пакеты. Рассмотрим, напирмер, requests.
import os import requests ex_path = os.path.split(requests.__file__)[0] + "/exceptions.py" print(ex_path)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/exceptions.py
with open(ex_path) as f: print(f.read())
# -*- coding: utf-8 -*-
«»»
requests.exceptions
~~~~~~~~~~~~~~~~~~~
This module contains the set of Requests’ exceptions.
«»»
from urllib3.exceptions import HTTPError as BaseHTTPError
class RequestException(IOError):
«»»There was an ambiguous exception that occurred while handling your
request.
«»»
def __init__(self, *args, **kwargs):
«»»Initialize RequestException with `request` and `response` objects.»»»
response = kwargs.pop(‘response’, None)
self.response = response
self.request = kwargs.pop(‘request’, None)
if (response is not None and not self.request and
hasattr(response, ‘request’)):
self.request = self.response.request
super(RequestException, self).__init__(*args, **kwargs)
class HTTPError(RequestException):
«»»An HTTP error occurred.»»»
class ConnectionError(RequestException):
«»»A Connection error occurred.»»»
class ProxyError(ConnectionError):
«»»A proxy error occurred.»»»
class SSLError(ConnectionError):
«»»An SSL error occurred.»»»
class Timeout(RequestException):
«»»The request timed out.
Catching this error will catch both
:exc:`~requests.exceptions.ConnectTimeout` and
:exc:`~requests.exceptions.ReadTimeout` errors.
«»»
class ConnectTimeout(ConnectionError, Timeout):
«»»The request timed out while trying to connect to the remote server.
Requests that produced this error are safe to retry.
«»»
class ReadTimeout(Timeout):
«»»The server did not send any data in the allotted amount of time.»»»
class URLRequired(RequestException):
«»»A valid URL is required to make a request.»»»
class TooManyRedirects(RequestException):
«»»Too many redirects.»»»
class MissingSchema(RequestException, ValueError):
«»»The URL schema (e.g. http or https) is missing.»»»
class InvalidSchema(RequestException, ValueError):
«»»See defaults.py for valid schemas.»»»
class InvalidURL(RequestException, ValueError):
«»»The URL provided was somehow invalid.»»»
class InvalidHeader(RequestException, ValueError):
«»»The header value provided was somehow invalid.»»»
class InvalidProxyURL(InvalidURL):
«»»The proxy URL provided is invalid.»»»
class ChunkedEncodingError(RequestException):
«»»The server declared chunked encoding but sent an invalid chunk.»»»
class ContentDecodingError(RequestException, BaseHTTPError):
«»»Failed to decode response content»»»
class StreamConsumedError(RequestException, TypeError):
«»»The content for this response was already consumed»»»
class RetryError(RequestException):
«»»Custom retries logic failed»»»
class UnrewindableBodyError(RequestException):
«»»Requests encountered an error when trying to rewind a body»»»
# Warnings
class RequestsWarning(Warning):
«»»Base warning for Requests.»»»
pass
class FileModeWarning(RequestsWarning, DeprecationWarning):
«»»A file was opened in text mode, but Requests determined its binary length.»»»
pass
class RequestsDependencyWarning(RequestsWarning):
«»»An imported dependency doesn’t match the expected version range.»»»
pass
url = "https://github.com/not_found" try: response = requests.get(url, timeout=30) response.raise_for_status() except requests.Timeout: print("ошибка timeout, url:", url) except requests.HTTPError as err: code = err.response.status_code print("ошибка url: {0}, code: {1}".format(url, code)) except requests.RequestException: print("ошибка скачивания url: ", url) else: print(response.content)
ошибка url: https://github.com/not_found, code: 404
- Назовите три области, где можно было бы использовать операции с исключениями.
- Что произойдет с программой в случае исключения, если вы не предусмотрите его обработку?
- Как можно реализовать восстановление нормальной работы сценария после исключения?
- Назовите два способа возбуждения исключений в сценариях.
- Назовите два способа, с помощью которых можно было бы организовать выполнение заключительных операций независимо от того, возникло исключение или нет.
Обработка исключений Python – это своеобразный и весьма мощный механизм, который позволяет управлять приложением при определенных обстоятельствах. С его помощью удается избежать аварийного завершения ввиду непредвиденных и внезапных багов.
Соответствующая возможность в Питоне реализована посредством синтактических оборотов и конструкций. Они предназначаются для отлова и обработки исключения, чтобы утилита грамотного выполняла изначально предусмотренный алгоритм.
Определение
В коде при разработке контента нередко происходит возникновение разных ошибок и неполадок. Они создают препятствия по достижению желаемого изначально результата.
Далеко не каждый баг приводит к краху программы: некоторые из них ликвидируются на этапе компиляции, а какие-то не заметны до возникновения «особых» обстоятельств. Иногда можно даже увидеть в коде ошибки, которые от добросовестности разработчика никак не зависят. Они появляются при определенных условиях функционирования утилиты.
Сегодня в Питоне выделяют такие типы ошибок:
- синтаксические – причиной их возникновения становятся погрешности синтаксиса в коде;
- логические – возникают из-за логических неточностей алгоритмов;
- исключения – вызываются за счет некорректных действий юзеров или операционной системы.
Каждый вариант предусматривает собственные особенности и нюансы. Далее они будут рассмотрены более детально. Это поможет грамотно реагировать на баги и использовать имеющиеся знания для предельной результативности обработки исключений в Python.
Синтаксические
Возникают из-за того, что разработчик не соблюдал общепринятые нормы языка. Пример – пропустил круглую скобку в нужном месте или неграмотно прописал функции в блоке кодификации. Подобные погрешности хорошо отлавливаются посредством компилятора. Он сообщит программеру информацию об ошибке.
Вот пример, который покажет, что произойдет, если пропустить двойные кавычки при выводе строковых литералов:
Print (“Hello World!)
Программа при компиляции укажет следующий текст:
File “main.py”, line 1
Print (“Hello World!)
^
SyntaxError: EOL while scanning string literal
Логические
Это – наиболее сложный вариант для обнаружения. Связано это с тем, что отлов подобных неполадок не осуществляется при компиляции. Аналогичным образом дела обстоят и с готовым приложением.
Логические ошибки – это следствие недостатков в логике задействованных алгоритмов. Из-за этого программер не может достичь желаемого итога.
Print (avg(10, 20))
Соответствующий код – это пример работы функции, которая отвечает за поиск среднего значения двух чисел. Она не будет работать в подобном виде. Связано это с логической ошибкой – формула прописана неправильно.
Исключения
Исключения в Python – это последний тип багов. Он будет проявляться в зависимости от наличия тех или иных обстоятельств, меняющих ход работы программы.
Исключения обычно видно во время обработки кодификации. Ниже — пример, который указывает на невозможность проведения операции «деление на ноль»:
Print (10/0)
Если попытаться вывести на экран результат, утилита вызовет следующий код?
Результат выполнения программы – это исключительная ситуация. Следствием станет аварийное завершение работы и вывод ошибки на дисплей. Тут отобразится файл, а также номер строчки кода, где обнаружено исключение ZeroDivisionError. Ниже – это краткое описание бага (Division by zero).
Перехват
Далее исключения будут рассмотрены более детально. Чтобы их наличие не могло дать прерывание программы, нужно грамотно провести его обработку. Достижение результата осуществляется посредством специальных механизмов. Они предотвращают все непредвиденные ситуации и используются на практике довольно часто.
О перехвате необходимо запомнить следующее:
- процесс помогает миновать сбои в работе алгоритма;
- достигнуть результата удается за счет расширения возможностей блока кода, которые позволяют действовать с учетом новых обстоятельств.
Вот пример попытки запуска утилиты, которая открывает текстовый документ:
Print (“Program started”)
Print (“Opening file…”)
F = open (“data.txt”)
Print (“Program finished”)
После того, как приложение будет обработано, на экране появится такая надпись:
Здесь происходит следующее:
- Print написано грамотно.
- Утилита не будет выполняться, так как файл с указанным именем не найден на жестком диске устройства.
- Программное обеспечение сгенерировало исключение FileNotFoundError. Оно указывает на неполадки при вводе или выводе.
- Последняя строчка кода – это сообщение о завершении утилиты. Она не отображается. Это свидетельствует о том, что далеко не все операции, предусмотренные ПО, будут выполнены из-за обнаруженного исключения.
Теперь можно рассмотреть блок try except. Он позволит обработать исключительную ситуацию без завершения уже запущенного приложения со стороны пользователя:
Print (“Program started”)
Try:
Print (“Opening file…”)
F = open (“data.txt”)
Except:
Print (“File not found”)
Print (“Program finished”)
Здесь:
- Блок try имеет опасную кодификацию, которая способна прервать функционирование утилиты.
- Except отвечает за инструкцию, которую необходимо обработать при баге.
- Программа вследствие обработки кодификации не завершается – об этом свидетельствует последний вывод функции print.
Данный прием при грамотном использовании позволяет в случае неполадок продолжить работу с софтом.
Несколько except
Теперь создадим несколько блоков except. В зависимости от того, какой тип исключения нужно обработать, будет меняться итоговый результат. Обычно соответствующей операции подлежат частные случаи, после – общие.
Вот – наглядный пример exceptions.
Вложенные и else
Блоки типа try except могут выступать в виде вложенных, чтобы обеспечивать гибкое управление исключениями в Python.
Для соответствующей кодификации требуется запомнить следующее:
- Она отвечает за демонстрацию попытки открытия текстового файла с записью в него определенной строки.
- Каждая цель задействует собственный блок try.
- Тут применяется конструкция с else, которая выполняется в случае, если в утилите нет корректировок.
- В приведенном примере else будет работать, если операция write будет успешно выполнена.
- По умолчанию документ открывается на чтение в текстовом режиме. При его открытии необходимо задействовать работу «w». Он отвечает за запись.
Если файла не было ранее на жестком диске, он будет создан в виде нового. В противном случае – производится перезапись. Все манипуляции выводятся на дисплей посредством print.
Finally
Случается и так, что в утилите обрабатывают определенные запросы, независимо от вызова исключений. На помощь придет блок finally. Он содержит инструкции, которые будут выполняться всегда.
Пример помогает улучшить приложение путем добавления возможности закрывать текстовый документ.
Чтобы упростить процесс коддинга, была разработана конструкция with/as. Она автоматизирует некоторые методы. Пример – закрытие файла заданного объекта. Результат все равно выводится через print.
Утилита обрабатывает все типы исключений, во всех ситуациях завершается естественным путем. Заданный алгоритм не будет экстренно прерываться.
Управление
Упомянутый ЯП дает возможность создания пользовательских исключений. Логирование программы сюда тоже включено.
Исключения пользователя
Обычно они в нужных ситуациях вызываются самостоятельно. Чтобы применить оные, необходимо задействовать ключевое слово raise. Далее указывается создание нового объекта типа Exception. Его удастся позже обработать через обычные конструкции try-except:
print("Program started")
try:
raise Exception("User Exception!")
except Exception as e:
print(str(e))
print("Program finished")
Program started
User Exception!
Program finished
При создании собственного исключения нужно запомнить следующее:
- Для этого требуется создать новый класс, который будет наследником базового типа Exception.
- Соответствующий прием позволит осуществлять запуск особых видов исключений в ситуациях, когда юзер ведет себя не по заданному алгоритму.
- В конструкторе Exception прописывается текст исключения.
- После его обработки и схватывания удается извлечь оный через str.
Вот пример с исключением типа raises NegativeAge. Отвечает за невозможность указания отрицательного возраста юзера:
Записывание в лог
В процессе изучения исключений, print, а также raise, recent call last, most recent call и иных особенностей Питона, может потребоваться запись в лог. Она помогает выводить спецсообщения, которые никак не отражаются на работоспособности утилиты.
Для использования библиотеки логов требуется произвести импорт в верхней части кодификации. Существуют различные типы логов, отобразить которые поможет команда logging.
Здесь:
- Клиенту доступны разные виды сообщений, которые реализуются не через print. Задействованы они обычно при разработке, лишний текст в консоли не отображается.
- Logging позволяет осуществлять запись в лог.
- Уровень INFO – это указатель на то, что сообщения уровней ниже (debug) не отображаются в логе.
Итоговый код:
В конце добавляется строка сообщения о типе одного сработавшего исключения в Python.
Иерархия
Исключения обладают строгой иерархией, которую нужно знать как print, in module, traceback и иные особенности языка. Вершина – это BaseException, который включает в себя все существующие типы исключений:
- SystemExit – выход через sys.exit;
- KeyboardInterrupt – прерывание пользователем;
- GeneratorExit – отображается при вызове методы close для generator;
- Exception – связь обычных несистемных исключений.
Здесь – таблица с исключениями несистемного плана в классе Exception.