Введение в тему
Зачастую возникают ситуации, когда программа или скрипт работают не так, как задумывал программист. Чаще всего это бывает из-за ввода неожиданных данных. Для обработки таких ситуаций в языке программирования Python есть конструкция try except else finally. Это называется обработкой исключений и позволяет контролировать аварийные случаи. Об этом мощном инструменте мы и поговорим в данном уроке.
Что такое исключения
Работа программиста во многом связана с возникающими в коде ошибками. Их приходится находить и исправлять. Особенно опасны так называемые гейзенбаги – ошибки, которые сложно воспроизвести. Так же существуют скрытые ошибки, их ещё можно назвать логическими. Ещё есть ошибки, которые и вовсе не зависят от программы. Представьте, у Вас есть программа-скрапер, которая автоматически скачивает картинки из соцсети. Заходит она на очередную страницу… А сервер сети поломался. Программа выдаст ошибку.
Если говорить именно о Питоне, то сложность ещё и в том, что это не компилируемый, а интерпретируемый язык, то есть код выполняется «на лету», строка за строкой. Это означает, что у Пайтон-программиста нет возможности отловить ошибки на этапе компиляции. Ещё одна сложность заключается в том, что Python – язык со строгой, но динамической типизацией. Частично это решается в последних версиях языка средством под названием «аннотирование типов», но полностью проблемы не устраняет.
И так, существуют следующие виды ошибок:
- Синтаксические – когда программист нарушает правила самого языка, к примеру, допускает опечатку в ключевом слове;
- Логические – когда в коде используется не верная логика;
- Ввода – когда программист предполагал от пользователя ввода одних данных, а введены другие. К примеру, создатель сайта задумывал, что число в форме будет указано с использованием точки в качестве разделителя, а пользователь ввёл «3,14». Именно этот вид ошибок – излюбленная лазейка хакеров.
Синтаксические ошибки – самые простые, поскольку интерпретатор сам сообщит Вам о них при попытке запустить скрипт.
Простой пример, напечатали команду print с большой буквы:
Print('Hello World!')
# Вывод
Traceback (most recent call last):
File "C:/Users/ivand/PycharmProjects/pythonProject/main.py", line 1, in <module>
Print('Hello World!')
NameError: name 'Print' is not defined
Process finished with exit code 1
Логические ошибки – самые сложные в обработке. Сложность в том, что скрипт запускается и не выдаёт никаких исключений, но результат работы отличается от ожидаемого. В чём причина и где её искать? Понятно, что использован не правильный алгоритм. В таких ситуациях можно посоветовать разбить алгоритм на части и проверять значение переменных в контрольных точках. Вот пример такой ошибки:
from random import randint
random_list = 5
sorted_list = []
for i in range(random_list):
sorted_list.append(randint(1, 99))
print(sorted_list)
for i in range(random_list - 1):
for j in range(random_list - i - 1):
if sorted_list[j] > sorted_list[j + 1]:
sorted_list[j] = sorted_list[j + 1]
print(sorted_list)
# Вывод:
[95, 57, 16, 29, 82]
[16, 16, 16, 29, 82]
В этом примере программист хотел сделать сортировку пузырьком, но допустил ошибку. А Вы сможете её найти?
Ошибки ввода, как уже говорилось, это ошибки, чаще всего возникающие из-за того, что программист и пользователь не поняли друг друга. Вот код примера, приведённого выше:
x_var = input('Введите число и мы его разделим на 10 \n')
print('Результат деления:', float(x_var) / 10)
# Вывод:
Введите число и мы его разделим на 10
3,14
Traceback (most recent call last):
File "C:/Users/ivand/PycharmProjects/pythonProject/main.py", line 2, in <module>
print('Результат деления:', float(x_var) / 10)
ValueError: could not convert string to float: '3,14'
Как вы видите, интерпретатор «выбрасывает» исключение «ValueError» — ошибка значения и останавливает выполнение кода.
Перехват исключений
Если Вам не подходит стандартное поведение языка при возникновении исключений – остановка выполнения, Вы можете перехватить исключение и обработать его. Для таких ситуаций и существует конструкция try except. Данный механизм Python позволяет контролировать непредвиденные ситуации и действовать исходя из новых условий. Проиллюстрируем это используя предыдущий пример:
x_var = input('Введите число и мы его разделим на 10 \n')
try:
print('Результат деления:', float(x_var) / 10)
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
print('Программа завершена')
# Вывод:
Введите число и мы его разделим на 10
3,14
Вы ввели число с запятой, а надо с точкой
Программа завершена
Несколько блоков except
Можно использовать несколько блоков except и обрабатывать в каждом блоке отдельный вид ошибки. Немного перепишем программу из предыдущего примера:
x_var = input('Введите число и мы разделим на него 10 \n')
try:
print('Результат деления:', 10 / float(x_var))
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
except ZeroDivisionError:
print('Вы ввели ноль, но на него делить нельзя')
print('Программа завершена')
# Вывод:
Введите число и мы разделим на него 10
0
Вы ввели ноль, но на него делить нельзя
Программа завершена
Хорошей практикой является написание сперва блоков для конкретных ошибок, а затем для общих случаев, поскольку всех ситуаций не предусмотреть:
x_var = input('Введите число и мы разделим на него 10 \n')
try:
Print('Результат деления:', 10 / float(x_var))
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
except ZeroDivisionError:
print('Вы ввели ноль, но на него делить нельзя')
except:
print('Не знаю что, но что-то точно пошло не так')
print('Программа завершена')
# Вывод:
Введите число и мы разделим на него 10
10
Не знаю что, но что-то точно пошло не так
Программа завершена
Вложенные блоки и else
Блоки try-except можно вкладывать друг в друга, если в этом есть необходимость.
Здесь же мы используем блок else. Этот блок должен содержать код, который выполнится если не возникнет исключений.
x_var = input('Введите число и мы разделим на него 10 \n')
try:
result = 10 / float(x_var)
try:
print('Результат деления:', result)
except:
print('Не знаю что, но что-то точно пошло не так')
else:
print('Полёт нормальный')
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
except ZeroDivisionError:
print('Вы ввели ноль, но на него делить нельзя')
else:
print('Программа выполнена без ошибок')
print('Программа завершена')
# Вывод:
Введите число и мы разделим на него 10
10
Результат деления: 1.0
Полёт нормальный
Программа выполнена без ошибок
Программа завершена
Кстати, здесь допущена логическая ошибка. Найдёте?
Finally
Встречаются ситуации, когда необходимо выполнить какую-то часть кода в независимости от того, было исключение или нет. Для этого существует блок finally:
try:
result = 10 / float(x_var)
try:
Print('Результат деления:', result)
except:
print('Не знаю что, но что-то точно пошло не так')
else:
print('Полёт нормальный')
except ValueError:
print('Вы ввели число с запятой, а надо с точкой')
except ZeroDivisionError:
print('Вы ввели ноль, но на него делить нельзя')
finally:
print('Программа завершена')
# Вывод:
Введите число и мы разделим на него 10
10
Не знаю что, но что-то точно пошло не так
Программа завершена
Управление исключениями
В Пайтоне есть возможность создавать свои виды исключений. Ниже мы рассмотрим как это делать, а ещё такую важную вещь как логгирование.
Пользовательские исключения
В Python есть ключевое слово raise. Нужно оно для того чтоб самостоятельно вызывать исключения:
raise Exception("Моя ошибка")
# Вывод:
Traceback (most recent call last):
File "C:/Users/ivand/PycharmProjects/pythonProject/main.py", line 1, in <module>
raise Exception("Моя ошибка")
Exception: Моя ошибка
Такие ошибки тоже можно ловить в try и обрабатывать в except:
x_var = float(input('Введите число\n'))
try:
if x_var > 10:
raise Exception()
except:
print('Что-то пошло не так. Возможно, число слишком большое')
# Вывод:
Введите число
11
Что-то пошло не так. Возможно, число слишком большое
Для того чтобы создать свой тип исключения, необходимо объявить новый класс и унаследовать его от базового типа Exception. Текст ошибки можно передавать используя дандер метод __str__:
class MyException(Exception):
def __str__(self):
return 'Число слишком большое'
x_var = float(input('Введите число\n'))
try:
if x_var > 10:
raise MyException()
except MyException:
print(MyException())
# Вывод:
Введите число
11
Число слишком большое
Так же, текст ошибки можно передавать переопределяя родительский атрибут message:
class MyException(Exception):
def __init__(self):
self.message = 'Число слишком большое'
super().__init__(self.message)
x_var = float(input('Введите число\n'))
try:
if x_var > 10:
raise MyException()
except MyException:
print(MyException())
# Вывод:
Введите число
11
Число слишком большое
Раз мы объявили метод __init__, следует сказать, что в него можно передавать аргументы:
class MyException(Exception):
def __init__(self, x):
self.x = x
self.message = 'Число {} слишком большое'.format(self.x)
super().__init__(self.message)
x_var = float(input('Введите число\n'))
if x_var > 10:
raise MyException(x_var)
# Вывод:
Введите число
11
Traceback (most recent call last):
File "C:/Users/ivand/PycharmProjects/pythonProject/main.py", line 9, in <module>
raise MyException(x_var)
__main__.MyException: Число 11.0 слишком большое
Запись в лог
Часто для отладки программ используют логгирование. Это вывод, чаще всего в отдельный файл, каких-то сообщений, содержащих информацию о том, как программа работает. В том числе, писать в лог можно и текст исключений. В Питоне для этого создали специальный модуль и даже включили его в стандартную библиотеку. Сперва его надо импортировать в Ваш код, а затем указать тип лога:
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug(" Сообщения про отладку")
logging.info(" Информационные сообщения")
logging.warning(" Предупреждения")
logging.error(" Сообщения с ошибками")
logging.critical(" Ну очень важные сообщения")
# Вывод:
DEBUG:root: Сообщения про отладку
INFO:root: Информационные сообщения
WARNING:root: Предупреждения
ERROR:root: Сообщения с ошибками
CRITICAL:root: Ну очень важные сообщения
Параметр level= указывает, сообщения какого уровня заносить в лог. К примеру, если указать ‘level= logging.ERROR’, то логгироваться будут только сообщения уровня error и critical. Объединим логгирование и обработку исключений:
import logging
logging.basicConfig(filename="log.txt", level=logging.WARNING)
try:
print(10 / 0)
except Exception:
logging.error(str(Exception))
Содержимое файла log.txt:
ERROR:root:<class 'Exception'>
Иерархия исключений
В Python есть иерархия исключений. Это происходит из-за того, что их классы наследуются друг от друга. Вот полный список:
BaseException — базовое исключение, от которого берут начало все остальные
+SystemExit — исключение, порождаемое функцией sys.exit при выходе из программы
+KeyboardInterrupt — порождается при прерывании программы пользователем (обычно сочетанием клавиш Ctrl+C)
+GeneratorExit — порождается при вызове метода close объекта generator
+Exception – исключения
++StopIteration — порождается встроенной функцией next, если в итераторе больше нет элементов
++StopAsyncIteration — используется для остановки асинхронного прохода
++ArithmeticError — арифметическая ошибка
+++FloatingPointError
+++OverflowError
+++ZeroDivisionError
++AssertionError— выражение в функции assert ложно
++AttributeError — объект не имеет данного атрибута (значения или метода)
++BufferError— операция, связанная с буфером, не может быть выполнена
++EOFError— функция наткнулась на конец файла и не смогла прочитать то, что хотела
++ImportError — не удалось импортирование модуля или его атрибута
+++ModuleNotFoundError
++LookupError— некорректный индекс или ключ
+++IndexError
+++KeyError
++MemoryError— недостаточно памяти
++NameError — не найдено переменной с таким именем
+++UnboundLocalError
++OSError — ошибка, связанная с системой
+++BlockingIOError
+++ChildProcessError
+++ConnectionError
++++BrokenPipeError
++++ConnectionAbortedError
++++ConnectionRefusedError
++++ConnectionResetError
+++FileExistsError
+++FileNotFoundError
+++InterruptedError
+++IsADirectoryError
+++NotADirectoryError
+++PermissionError
+++ProcessLookupError
+++TimeoutError
++ReferenceError — попытка доступа к атрибуту со слабой ссылкой
++RuntimeError — возникает, когда исключение не попадает ни под одну из других категорий
+++NotImplementedError
+++RecursionError
++SyntaxError — синтаксическая ошибка
++IndentationError
++TabError
++SystemError — внутренняя ошибка
++TypeError — операция применена к объекту несоответствующего типа
++ValueError — функция получает аргумент правильного типа, но некорректного значения
+++UnicodeError
++++UnicodeDecodeError
++++UnicodeEncodeError
++++UnicodeTranslateError
++Warning — предупреждение
+++DeprecationWarning
+++PendingDeprecationWarning
+++RuntimeWarning
+++SyntaxWarning
+++UserWarning
+++FutureWarning
+++ImportWarning
+++UnicodeWarning
+++BytesWarning
+++ResourceWarning