Можно писать программы, которые обрабатывают выбранные исключения. Посмотрите на следующий пример, который запрашивает ввод у пользователя до тех пор, пока не будет введено правильное целое число, но позволяет пользователю прерывать программу, используя Ctrl-C или что-либо поддерживаемое операционной системой. Обратите внимание, что сгенерированное пользователем прерывание сигнализируется возбуждением исключения KeyboardInterrupt
.
while True: try: x = int(input("Пожалуйста, введите целое число: ")) break except ValueError: print("Это не целое число. Попробуйте снова...")
try/except
работает следующим образом
:
- Сначала выполняется инструкция
try
— код между ключевыми словамиtry
иexcept
. - Если исключение не возникает, инструкция
except
пропускается и выполнение оператораtry
завершается. - Если во время выполнения кода в инструкции
try
возникает исключение, остальная часть кода этого блока пропускается. Затем, если тип исключения соответствует исключению, записанному после ключевого словаexcept
, блок кода этой инструкцииexcept
выполняется, а затем выполнение программы продолжается после всей конструкцииtry
. - Если возникает исключение, которое не соответствует исключению, записанному в инструкции
except
, оно передается внешним операторамtry
. Если обработчик не найден, то это считается необработанным исключением, следовательно выполнение останавливается с сообщением об ошибке выполнения.
Оператор try
может содержать несколько инструкций except
, чтобы указать обработчики для различных исключений. В этом случае будет выполнен только один обработчик. Обработчики обрабатывают исключения, возникающие только в соответствующей конструкции try
, а не в других обработчиках, например вложенных, того же оператора try
. Инструкция except
может иметь несколько исключений в виде кортежа, заключенного в скобки, например:
except (RuntimeError, TypeError, NameError): pass
Класс в инструкции except
совместим с исключением, если это тот же самый класс или его базовый класс, но не наоборот. Инструкция except
, перечисляющая производный класс, не совместима с базовым классом. Например, следующий код будет печатать B
, C
, D
в таком порядке:
class B(Exception): pass class C(B): pass class D(C): pass for cls in [B, C, D]: try: raise cls() except D: print('D') except C: print('C') except B: print('B')
Обратите внимание, что если бы инструкции except
были отменены (с первым исключением B), то вывелось бы B
, B
, B
— срабатывает первое совпадающее предложение except
.
Последняя инструкция except
может опустить имя исключения. Используйте это с крайней осторожностью, таким образом можно легко замаскировать реальную ошибку программирования! Здесь можно использовать sys.exc_info()
или модуль traceback
для вывода сообщения об ошибке или ее сохранения для дельнейшего анализа, а затем повторно вызвать исключение при помощи оператора raise
, что позволяет вызывающей стороне также обработать исключение:
import traceback try: f = open('myfile.txt') s = f.readline() i = int(s.strip()) except OSError as err: print("OS error: {0}".format(err)) except ValueError: print("Не удалось преобразовать данные в целое число.") except: print("Непредвиденная ошибка...") # сохраняем исключение для дальнейшего анализа. with open('trace.txt', 'a') as fp: traceback.print_exc(file=fp) # повторный вызов исключения, если это необходимо. raise
В примере выше, сведения об ошибке сохраняются в файл trace.txt
с использованием встроенной функции open()
. Лучшей практикой сохранения исключений для дальнейшего анализа в подобных ситуациях является применение модуля logging
.
Конструкция try/except
может содержать необязательную инструкцию else
, которая при наличии должна следовать за всеми инструкциями except
. Блок кода в инструкции else
будет выполнен в том случае, если код в инструкции try
не вызывает исключения. Например:
for arg in sys.argv[1:]: try: f = open(arg, 'r') except OSError: print('не удается открыть', arg) else: print(arg, 'имеет', len(f.readlines()), 'строк') f.close()
Использование инструкции else
лучше, чем добавление дополнительного кода в инструкцию try
, поскольку позволяет избежать случайного перехвата исключения, которое не было вызвано кодом, защищенным конструкцией try/except
.
Когда возникает исключение, оно может иметь связанное значение, также известное как аргумент исключения. Наличие и тип аргумента зависят от типа исключения.
Инструкция except
может указывать переменную после имени исключения. Переменная привязывается к экземпляру исключения с аргументами, хранящимися в экземпляре instance.args
. Для удобства, экземпляр исключения определяет __str__()
, так что аргументы могут быть напечатаны непосредственно без необходимости ссылаться instance.args
. Кроме того, можно создать экземпляр исключения прежде, чем вызвать его и добавить любые атрибуты к нему по желанию.
try: raise Exception('spam', 'eggs') except Exception as inst: # экземпляр исключения print(type(inst)) # аргументы, хранящиеся внутри print(inst.args) # __str__ позволяет печатать args напрямую, но может # быть переопределен в подклассах исключений print(inst) # распаковка аргументов x, y = inst.args print('x =', x) print('y =', y) # <class 'Exception'> # ('spam', 'eggs') # ('spam', 'eggs') # x = spam # y = eggs
Если исключение имеет аргументы, они печатаются как последняя часть («деталь») сообщения для необработанных исключений.
Обработчики исключений обрабатывают не только исключения возникающие непосредственно в предложении try
, но также если они возникают внутри функций, которые вызываются, даже косвенно, в коде оператора try
. Например:
def this_fails(): x = 1/0 try: this_fails() except ZeroDivisionError as err: print('Handling run-time error:', err) #Handling run-time error: division by zero
Исключения (exceptions) — ещё один тип данных в python. Исключения необходимы для того, чтобы сообщать программисту об ошибках.
Самый простейший пример исключения — деление на ноль:
>>> 100 / 0 Traceback (most recent call last): File "", line 1, in 100 / 0 ZeroDivisionError: division by zero
Разберём это сообщение подробнее: интерпретатор нам сообщает о том, что он поймал исключение и напечатал информацию (Traceback (most recent call last)).
Далее имя файла (File «»). Имя пустое, потому что мы находимся в интерактивном режиме, строка в файле (line 1);
Выражение, в котором произошла ошибка (100 / 0).
Название исключения (ZeroDivisionError) и краткое описание исключения (division by zero).
Разумеется, возможны и другие исключения:
>>> 2 + '1' Traceback (most recent call last): File "", line 1, in 2 + '1' TypeError: unsupported operand type(s) for +: 'int' and 'str' >>> int('qwerty') Traceback (most recent call last): File "", line 1, in int('qwerty') ValueError: invalid literal for int() with base 10: 'qwerty'
В этих двух примерах генерируются исключения TypeError и ValueError соответственно. Подсказки дают нам полную информацию о том, где порождено исключение, и с чем оно связано.
Рассмотрим иерархию встроенных в python исключений, хотя иногда вам могут встретиться и другие, так как программисты могут создавать собственные исключения. Данный список актуален для python 3.3, в более ранних версиях есть незначительные изменения.
- BaseException — базовое исключение, от которого берут начало все остальные.
- SystemExit — исключение, порождаемое функцией sys.exit при выходе из программы.
- KeyboardInterrupt — порождается при прерывании программы пользователем (обычно сочетанием клавиш Ctrl+C).
- GeneratorExit — порождается при вызове метода close объекта generator.
- Exception — а вот тут уже заканчиваются полностью системные исключения (которые лучше не трогать) и начинаются обыкновенные, с которыми можно работать.
- StopIteration — порождается встроенной функцией next, если в итераторе больше нет элементов.
- ArithmeticError — арифметическая ошибка.
- FloatingPointError — порождается при неудачном выполнении операции с плавающей запятой. На практике встречается нечасто.
- OverflowError — возникает, когда результат арифметической операции слишком велик для представления. Не появляется при обычной работе с целыми числами (так как python поддерживает длинные числа), но может возникать в некоторых других случаях.
- ZeroDivisionError — деление на ноль.
- AssertionError — выражение в функции assert ложно.
- AttributeError — объект не имеет данного атрибута (значения или метода).
- BufferError — операция, связанная с буфером, не может быть выполнена.
- EOFError — функция наткнулась на конец файла и не смогла прочитать то, что хотела.
- ImportError — не удалось импортирование модуля или его атрибута.
- 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 — возникает, когда абстрактные методы класса требуют переопределения в дочерних классах.
- SyntaxError — синтаксическая ошибка.
- IndentationError — неправильные отступы.
- TabError — смешивание в отступах табуляции и пробелов.
- IndentationError — неправильные отступы.
- SystemError — внутренняя ошибка.
- TypeError — операция применена к объекту несоответствующего типа.
- ValueError — функция получает аргумент правильного типа, но некорректного значения.
- UnicodeError — ошибка, связанная с кодированием / раскодированием unicode в строках.
- UnicodeEncodeError — исключение, связанное с кодированием unicode.
- UnicodeDecodeError — исключение, связанное с декодированием unicode.
- UnicodeTranslateError — исключение, связанное с переводом unicode.
- Warning — предупреждение.
Теперь, зная, когда и при каких обстоятельствах могут возникнуть исключения, мы можем их обрабатывать. Для обработки исключений используется конструкция try — except.
Первый пример применения этой конструкции:
>>> try: ... k = 1 / 0 ... except ZeroDivisionError: ... k = 0 ... >>> print(k) 0
В блоке try мы выполняем инструкцию, которая может породить исключение, а в блоке except мы перехватываем их. При этом перехватываются как само исключение, так и его потомки. Например, перехватывая ArithmeticError, мы также перехватываем FloatingPointError, OverflowError и ZeroDivisionError.
>>> try: ... k = 1 / 0 ... except ArithmeticError: ... k = 0 ... >>> print(k) 0
Также возможна инструкция except без аргументов, которая перехватывает вообще всё (и прерывание с клавиатуры, и системный выход и т. д.). Поэтому в такой форме инструкция except практически не используется, а используется except Exception. Однако чаще всего перехватывают исключения по одному, для упрощения отладки (вдруг вы ещё другую ошибку сделаете, а except её перехватит).
Ещё две инструкции, относящиеся к нашей проблеме, это finally и else. Finally выполняет блок инструкций в любом случае, было ли исключение, или нет (применима, когда нужно непременно что-то сделать, к примеру, закрыть файл). Инструкция else выполняется в том случае, если исключения не было.
>>> f = open('1.txt') >>> ints = [] >>> try: ... for line in f: ... ints.append(int(line)) ... except ValueError: ... print('Это не число. Выходим.') ... except Exception: ... print('Это что ещё такое?') ... else: ... print('Всё хорошо.') ... finally: ... f.close() ... print('Я закрыл файл.') ... # Именно в таком порядке: try, группа except, затем else, и только потом finally. ... Это не число. Выходим. Я закрыл файл.
Содержание:развернуть
- Как устроен механизм исключений
- Как обрабатывать исключения в 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 можно ознакомиться в официальной документации.
Время прочтения
10 мин
Просмотры 32K
Одним из недостатков гибких языков, таких как Python, является предположение, что если что-то работает, то скорее всего оно сделано правильно. Я хочу написать скромное руководство по эффективному использованию исключений в Python, правильной их обработке и логировании.
Эффективная обработка исключений
Введение
Давайте рассмотрим следующую систему. У нас есть микросервис, который отвечает за:
· Прослушивание событий о новом заказе;
· Получение заказа из базы данных;
· Проверку состояния принтера;
· Печать квитанции;
· Отправка квитанции в налоговую систему (IRS).
В любой момент может сломаться что угодно. У вас могут возникнуть проблемы с объектом заказа, в котором может не быть нужной информации, или в принтере может закончиться бумага, или же сервис налоговой не будет работать, и вы не сможете синхронизировать с ними квитанцию об оплате, а может быть ваша база данных окажется недоступна.
Ваша задача правильно и проактивно реагировать на любую ситуацию, чтобы избежать ошибок при обработке новых заказов.
И примерно вот такой код на этот случай пишут люди (он, конечно, работает, но плохо и неэффективно):
class OrderService:
def emit(self, order_id: str) -> dict:
try:
order_status = status_service.get_order_status(order_id)
except Exception as e:
logger.exception(
f"Order {order_id} was not found in db "
f"to emit. Error: {e}."
)
raise e
(
is_order_locked_in_emission,
seconds_in_emission,
) = status_service.is_order_locked_in_emission(order_id)
if is_order_locked_in_emission:
logger.info(
"Redoing emission because "
"it was locked in that state after a long time! "
f"Time spent in that state: {seconds_in_emission} seconds "
f"Order: {order_id}, "
f"order_status: {order_status.value}"
)
elif order_status == OrderStatus.EMISSION_IN_PROGRESS:
logger.info("Aborting emission request because it is already in progress!")
return {"order_id": order_id, "order_status": order_status.value}
elif order_status == OrderStatus.EMISSION_SUCCESSFUL:
logger.info(
"Aborting emission because it already happened! "
f"Order: {order_id}, "
f"order_status: {order_status.value}"
)
return {"order_id": order_id, "order_status": order_status.value}
try:
receipt_note = receipt_service.create(order_id)
except Exception as e:
logger.exception(
"Error found during emission! "
f"Order: {order_id}, "
f"exception: {e}"
)
raise e
try:
broker.emit_receipt_note(receipt_note)
except Exception as e:
logger.exception(
"Emission failed! "
f"Order: {order_id}, "
f"exception: {e}"
)
raise e
order_status = status_service.get_order_status(order_id)
return {"order_id": order_id, "order_status": order_status.value}
Сначала я сосредоточусь на том, что OrderService
слишком много знает, и все эти данные делают его чем-то вроде blob, а чуть позже расскажу о правильной обработке и правильном логировании исключений.
Почему этот сервис — blob?
Этот сервис знает слишком много. Кто-то может сказать, что он знает только то, что ему нужно (то есть все шаги, связанные с формированием чека), но на самом деле он знает куда больше.
Он сосредоточен на создании ошибок (например, база данных, печать, статус заказа), а не на том, что он делает (например, извлекает, проверяет статус, генерирует, отправляет) и на том, как следует реагировать в случае сбоев.
В этом смысле мне кажется, что клиент учит сервис тому, какие исключения он может выдать. Если мы решим переиспользовать его на любом другом этапе (например, клиент захочет получить еще одну печатную копию более старого чека по заказу), мы скопируем большую часть этого кода.
Несмотря на то, что сервис работает нормально, поддерживать его трудно, и неясно, как один шаг соотносится с другим из-за повторяющихся блоков except между шагами, которые отвлекают наше внимание на вопрос «как» вместо того, чтобы думать о «когда».
Первое улучшение: делайте исключения конкретными
Давайте сначала сделаем исключения более точными и конкретными. Преимущества не видны сразу, поэтому я не буду тратить слишком много времени на объяснение этого прямо сейчас. Однако обратите внимание на то, как изменяется код.
Я выделю только то, что мы поменяли:
try:
order_status = status_service.get_order_status(order_id)
except Exception as e:
logger.exception(...)
raise OrderNotFound(order_id) from e
...
try:
...
except Exception as e:
logger.exception(...)
raise ReceiptGenerationFailed(order_id) from e
try:
broker.emit_receipt_note(receipt_note)
except Exception as e:
logger.exception(...)
raise ReceiptEmissionFailed(order_id) from e
Обратите внимание, что на этот раз я пользуюсь from e
, что является правильным способом создания одного исключения из другого и сохраняет полную трассировку стека.
Второе улучшение: не лезьте не в свое дело
Теперь, когда у нас есть кастомные исключения, мы можем перейти к мысли «не учите классы тому, что может пойти не так» — они сами скажут, если это случится!
# Services
class StatusService:
def get_order_status(order_id):
try:
...
except Exception as e:
raise OrderNotFound(order_id) from e
class ReceiptService:
def create(order_id):
try:
...
except Exception as e:
raise ReceiptGenerationFailed(order_id) from e
class Broker:
def emit_receipt_note(receipt_note):
try:
...
except Exception as e:
raise ReceiptEmissionFailed(order_id) from e
# Main class
class OrderService:
def emit(self, order_id: str) -> dict:
try:
order_status = status_service.get_order_status(order_id)
(
is_order_locked_in_emission,
seconds_in_emission,
) = status_service.is_order_locked_in_emission(order_id)
if is_order_locked_in_emission:
logger.info(
"Redoing emission because "
"it was locked in that state after a long time! "
f"Time spent in that state: {seconds_in_emission} seconds "
f"Order: {order_id}, "
f"order_status: {order_status.value}"
)
elif order_status == OrderStatus.EMISSION_IN_PROGRESS:
logger.info("Aborting emission request because it is already in progress!")
return {"order_id": order_id, "order_status": order_status.value}
elif order_status == OrderStatus.EMISSION_SUCCESSFUL:
logger.info(
"Aborting emission because it already happened! "
f"Order: {order_id}, "
f"order_status: {order_status.value}"
)
return {"order_id": order_id, "order_status": order_status.value}
receipt_note = receipt_service.create(order_id)
broker.emit_receipt_note(receipt_note)
order_status = status_service.get_order_status(order_id)
except OrderNotFound as e:
logger.exception(
f"Order {order_id} was not found in db "
f"to emit. Error: {e}."
)
raise
except ReceiptGenerationFailed as e:
logger.exception(
"Error found during emission! "
f"Order: {order_id}, "
f"exception: {e}"
)
raise
except ReceiptEmissionFailed as e:
logger.exception(
"Emission failed! "
f"Order: {order_id}, "
f"exception: {e}"
)
raise
else:
return {"order_id": order_id, "order_status": order_status.value}
Как вам? Намного лучше, правда? У нас есть один блок try
, который построен достаточно логично, чтобы понять, что произойдет дальше. Вы сгруппировали конкретные блоки, за исключением тех, которые помогают вам понять «когда» и крайние случаи. И, наконец, у вас есть блок else
, в котором описано, что произойдет, если все отработает как надо.
Кроме того, пожалуйста, обратите внимание на то, что я сохранил инструкции raise
без объявления объекта исключения. Это не опечатка. На самом деле, это правильный способ повторного вызова исключения: простой и немногословный.
Но это еще не все. Логирование продолжает меня раздражать.
Третье улучшение: улучшение логирования
Этот шаг напоминает мне принцип «говори, а не спрашивай», хотя это все же не совсем он. Вместо того, чтобы запрашивать подробности исключения и на их основе выдавать полезные сообщения, исключения должны выдавать их сами – в конце концов, я их конкретизировал!
### Exceptions
class OrderCreationException(Exception):
pass
class OrderNotFound(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id
super().__init__(
f"Order {order_id} was not found in db "
"to emit."
)
class ReceiptGenerationFailed(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id
super().__init__(
"Error found during emission! "
f"Order: {order_id}"
)
class ReceiptEmissionFailed(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id
super().__init__(
"Emission failed! "
f"Order: {order_id} "
)
### Main class
class OrderService:
def emit(self, order_id: str) -> dict:
try:
...
except OrderNotFound:
logger.exception("We got a database exception")
raise
except ReceiptGenerationFailed:
logger.exception("We got a problem generating the receipt")
raise
except ReceiptEmissionFailed:
logger.exception("Unable to emit the receipt")
raise
else:
return {"order_id": order_id, "order_status": order_status.value}
Наконец-то мои глаза чувствуют облегчение. Поменьше повторений, пожалуйста! Примите к сведению, что рекомендуемый способ выглядит так, как я написал его выше: logger.exception
(«ЛЮБОЕ СООБЩЕНИЕ»). Вам даже не нужно передавать исключение, поскольку его наличие уже подразумевается. Кроме того, кастомное сообщение, которое мы определили в каждом исключении с идентификатором order_id
, будет отображаться в логах, поэтому вам не нужно повторяться и не нужно оперировать внутренними данными об исключениях.
Вот пример вывода ваших логов:
❯ python3 testme.py
Unable to emit the receipt # <<-- My log message
Traceback (most recent call last):
File "/path/testme.py", line 19, in <module>
tryme()
File "/path/testme.py", line 14, in tryme
raise ReceiptEmissionFailed(order_id)
ReceiptEmissionFailed: Emission failed! Order: 10 # <<-- My exception message
Теперь всякий раз, когда я получаю это исключение, сообщение уже ясно и понятно, и мне не нужно помнить о логировании order_id
, который я сгенерировал.
Последнее улучшение: упрощение
После более детального рассмотрения нашего окончательного кода, он кажется лучше, теперь его легко читать и поддерживать.
Но управляет ли OrderService
бизнес-логикой? Я не думаю, что это сервис в общем смысле. Он больше похож на координацию вызовов настоящих сервисов обеспечивающих бизнес-логику, которая выглядит получше, чем паттерн facade.
Кроме того, можно заметить, что он запрашивает данные у status_service
, чтобы что-то с ними сделать. (Что, на этот раз, действительно разрушает идею «Говори, а не спрашивай»).
Перейдем к упрощению.
class OrderFacade: # Renamed to match what it actually is
def emit(self, order_id: str) -> dict:
try:
# NOTE: info logging still happens inside
status_service.ensure_order_unlocked(order_id)
receipt_note = receipt_service.create(order_id)
broker.emit_receipt_note(receipt_note)
order_status = status_service.get_order_status(order_id)
except OrderAlreadyInProgress as e:
# New block
logger.info("Aborting emission request because it is already in progress!")
return {"order_id": order_id, "order_status": e.order_status.value}
except OrderAlreadyEmitted as e:
# New block
logger.info(f"Aborting emission because it already happened! {e}")
return {"order_id": order_id, "order_status": e.order_status.value}
except OrderNotFound:
logger.exception("We got a database exception")
raise
except ReceiptGenerationFailed:
logger.exception("We got a problem generating the receipt")
raise
except ReceiptEmissionFailed:
logger.exception("Unable to emit the receipt")
raise
else:
return {"order_id": order_id, "order_status": order_status.value}
Мы только что создали новый метод ensure_order_unlocked
для нашего status_service
, который теперь отвечает за создание исключений/логирование в случае, если что-то идет не так.
Хорошо, а теперь скажите, насколько легче теперь стало это читать?
Я могу понять все return
при беглом просмотре. Я знаю, что происходит, когда все идет хорошо, и как крайние случаи могут привести к разным результатам. И все это без прокрутки взад-вперед.
Теперь этот код такой же простой, каким (в основном) должен быть любой код.
Обратите внимание, что я решил вывести объект исключения e в логах, поскольку под капотом он будет запускать str(e)
, который вернет сообщение об исключении. Я подумал, что было бы полезно говорить подробно, поскольку мы не используем log.exception
для этого блока, поэтому сообщение об исключении не будет отображаться.
Теперь давайте разберемся с некоторыми хитростями, которые помогут вам сделать код понятным для чтения и простым в обслуживании.
Эффективное создание исключений
Всегда классифицируйте свои исключения через базовое и расширяйте все конкретные исключения от него. С помощью этой полезной практики вы можете переиспользовать логику для связанного кода.
Исключения – это объекты, которые несут в себе информацию, поэтому не стесняйтесь добавлять кастомные атрибуты, которые могут помочь вам понять, что происходит. Не позволяйте своему бизнес-коду учить вас тому, как он должен быть построен, ведь с таким количеством сообщений и деталей потерять себя становится трудно.
# Base category exception
class OrderCreationException(Exception):
pass
# Specific error with custom message. Order id is required.
class OrderNotFound(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id # custom property
super().__init__(
f"Order {order_id} was not found in db "
f"to emit."
)
# Specific error with custom message. Order id is required.
class ReceiptGenerationFailed(OrderCreationException):
def __init__(self, order_id):
self.order_id = order_id # custom property
super().__init__(
"Error found during emission! "
f"Order: {order_id}"
)
В примере выше я мог бы выйти за рамки и расширить базовый класс, чтобы всегда получать order_id
, если мне это нужно. Этот совет поможет сохранить код сухим, поскольку мне не нужно быть многословным при создании исключений. Так можно использовать всего лишь одну переменную.
def func1(order_id):
raise OrderNotFound(order_id)
# instead of raise OrderNotFound(f"Can't find order {order_id}")
def func2(order_id):
raise OrderNotFound(order_id)
# instead of raise OrderNotFound(f"Can't find order {order_id}")
В тестировании также будет больше смысла, поскольку я могу сделать assert order_id
через строку.
assert e.order_id == order_id
# instead of assert order_id in str(e)
Ловим и создаем исключения эффективно
Еще одна вещь, которую люди часто делают неправильно – это отлавливают и повторно создают исключения.
Согласно PEP 3134 Python, делать нужно следующим образом.
Повторное создание исключения
Обычной инструкции raise
более чем достаточно.
try:
...
except CustomException as ex:
# do stuff (e.g. logging)
raise
Создание одного исключения из другого
Этот вариант особо актуален, поскольку он сохраняет всю трассировку стека и помогает вашей команде отлаживать основные проблемы.
try:
...
except CustomException as ex:
raise MyNewException() from ex
Эффективное логирование исключений
Еще один совет, который не позволит вам быть слишком многословным.
Используйте logger.exception
Вам не нужно логировать объект исключения. Функция exception
логгера предназначена для использования внутри блоков except
. Она уже обрабатывает трассировку стека с информацией о выполнении и отображает, какое исключение вызвало ее, с сообщением, установленном на уровне ошибки!
try:
...
except CustomException:
logger.exception("custom message")
А что, если это не ошибка?
Если по какой-то причине вы не хотите логировать исключение как ошибку, то возможно, это предупреждение или просто информация, как было показано выше.
Вы можете принять решение установить exc_info
в True
, если хотите сохранить трассировку стека. Кроме того, было бы неплохо использовать объект исключения внутри сообщения.
Источники
Документация Python:
· Python logging.logger.exception
· Python PEP 3134
Принципы и качество кода:
· Говори, а не спрашивай
· Паттерн facade
· Blob
— РЕГИСТРАЦИЯ
In this article, we will discuss how to catch all exceptions in Python using try, except statements with the help of proper examples. But before let’s see different types of errors in Python.
There are generally two types of errors in Python i.e. Syntax error and Exceptions. Let’s see the difference between them.
Difference between Syntax Error and Exceptions
Syntax Error: As the name suggests this error is caused by the wrong syntax in the code. It leads to the termination of the program.
Example: Syntax Error in Python
Python3
amount
=
10000
if
(amount >
2999
)
print
(
"You are eligible to purchase Dsa Self Paced"
)
Output:
SyntaxError: invalid syntax
Exceptions: Exceptions are raised when the program is syntactically correct, but the code resulted in an error. This error does not stop the execution of the program, however, it changes the normal flow of the program.
Example: Exception in Python
Python3
marks
=
10000
a
=
marks
/
0
print
(a)
Output:
ZeroDivisionError: division by zero
Try and Except Statement – Catching all Exceptions
Try and except statements are used to catch and handle exceptions in Python. Statements that can raise exceptions are kept inside the try clause and the statements that handle the exception are written inside except clause.
Example: Python catch all exceptions
Python3
a
=
[
1
,
2
,
3
]
try
:
print
(
"Second element = %d"
%
(a[
1
]))
print
(
"Fourth element = %d"
%
(a[
3
]))
except
:
print
(
"Error occurred"
)
Output
Second element = 2 An error occurred
In the above example, the statements that can cause the error are placed inside the try statement (second print statement in our case). The second print statement tries to access the fourth element of the list which is not there and this throws an exception. This exception is then caught by the except statement. Without specifying any type of exception all the exceptions cause within the try block will be caught by the except block. We can also catch a specific exception. Let’s see how to do that.
Catching Specific Exception
A try statement can have more than one except clause, to specify handlers for different exceptions. Please note that at most one handler will be executed. For example, we can add IndexError in the above code. The general syntax for adding specific exceptions are –
try: # statement(s) except IndexError: # statement(s) except ValueError: # statement(s)
Example: Catching specific exception in Python
Python3
def
fun(a):
if
a <
4
:
b
=
a
/
(a
-
3
)
print
(
"Value of b = "
, b)
try
:
fun(
3
)
fun(
5
)
except
ZeroDivisionError:
print
(
"ZeroDivisionError Occurred and Handled"
)
except
NameError:
print
(
"NameError Occurred and Handled"
)
Output
ZeroDivisionError Occurred and Handled
If you comment the line fun(3), the output will be
NameError Occurred and Handled
The output above is so because as soon as python tries to access the value of b, NameError occurs.
Note: For more information, refer to our Python Exception Handling Tutorial.
In this article, we will discuss how to catch all exceptions in Python using try, except statements with the help of proper examples. But before let’s see different types of errors in Python.
There are generally two types of errors in Python i.e. Syntax error and Exceptions. Let’s see the difference between them.
Difference between Syntax Error and Exceptions
Syntax Error: As the name suggests this error is caused by the wrong syntax in the code. It leads to the termination of the program.
Example: Syntax Error in Python
Python3
amount
=
10000
if
(amount >
2999
)
print
(
"You are eligible to purchase Dsa Self Paced"
)
Output:
SyntaxError: invalid syntax
Exceptions: Exceptions are raised when the program is syntactically correct, but the code resulted in an error. This error does not stop the execution of the program, however, it changes the normal flow of the program.
Example: Exception in Python
Python3
marks
=
10000
a
=
marks
/
0
print
(a)
Output:
ZeroDivisionError: division by zero
Try and Except Statement – Catching all Exceptions
Try and except statements are used to catch and handle exceptions in Python. Statements that can raise exceptions are kept inside the try clause and the statements that handle the exception are written inside except clause.
Example: Python catch all exceptions
Python3
a
=
[
1
,
2
,
3
]
try
:
print
(
"Second element = %d"
%
(a[
1
]))
print
(
"Fourth element = %d"
%
(a[
3
]))
except
:
print
(
"Error occurred"
)
Output
Second element = 2 An error occurred
In the above example, the statements that can cause the error are placed inside the try statement (second print statement in our case). The second print statement tries to access the fourth element of the list which is not there and this throws an exception. This exception is then caught by the except statement. Without specifying any type of exception all the exceptions cause within the try block will be caught by the except block. We can also catch a specific exception. Let’s see how to do that.
Catching Specific Exception
A try statement can have more than one except clause, to specify handlers for different exceptions. Please note that at most one handler will be executed. For example, we can add IndexError in the above code. The general syntax for adding specific exceptions are –
try: # statement(s) except IndexError: # statement(s) except ValueError: # statement(s)
Example: Catching specific exception in Python
Python3
def
fun(a):
if
a <
4
:
b
=
a
/
(a
-
3
)
print
(
"Value of b = "
, b)
try
:
fun(
3
)
fun(
5
)
except
ZeroDivisionError:
print
(
"ZeroDivisionError Occurred and Handled"
)
except
NameError:
print
(
"NameError Occurred and Handled"
)
Output
ZeroDivisionError Occurred and Handled
If you comment the line fun(3), the output will be
NameError Occurred and Handled
The output above is so because as soon as python tries to access the value of b, NameError occurs.
Note: For more information, refer to our Python Exception Handling Tutorial.
На чтение 13 мин Просмотров 4к. Опубликовано 12.07.2021
Содержание
- Введение в тему
- Что такое исключения
- Перехват исключений
- Несколько блоков except
- Вложенные блоки и else
- Finally
- Управление исключениями
- Пользовательские исключения
- Запись в лог
- Иерархия исключений
Введение в тему
Зачастую возникают ситуации, когда программа или скрипт работают не так, как задумывал программист. Чаще всего это бывает из-за ввода неожиданных данных. Для обработки таких ситуаций в языке программирования 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 Вы ввели число с запятой, а надо с точкой Программа завершена
Как Вы можете заметить, программа выполнена полностью. Об этом свидетельствует последняя строка вывода. В блок try необходимо заключить тот участок кода, в котором может возникнуть исключение, а в блоке except – его обработку. Обратите внимание, что в блоке except можно не указывать вид ошибки и тогда будет обработано любое возникшее в блоке try исключение.
Несколько блоков 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
Иногда, во время работы программы случаются непредвиденные ситуации. Например, попытка поделить число на ноль или чтение несуществующего файла вызовут ошибку и остановят выполнение программы. Такие ситуации в программировании называют исключительными ситуациями, а ошибки — исключениями (exception). Для разных ситуаций есть свои типы исключений:
2 / 0 # ZeroDivisionError: division by zero
open('non_existing_file.txt') # FileNotFoundError: No such file or directory
Как вы могли заметить, названия исключений довольно точно описывают причину возникновения ошибки. Это часть плана: исключения нужны для того, чтобы рассказать программисту что произошло. Однако, когда выбрасывается исключение, вместе с его типом на экран выводится ещё целая куча сопутствующей информации. Давайте рассмотрим такой пример:
# Файл example.py
def average(numbers):
return sum(numbers) / len(numbers)
average([1, 2, 3]) # Вернёт 2
average([]) # Сделает ба-бах!
Выйдет такая ошибка:
Traceback (most recent call last): # 6
File "example.py", line 5, in <module> # 5
average([]) # 4
File "example.py", line 2, in average # 3
return sum(numbers) / len(numbers) # 2
ZeroDivisionError: division by zero # 1
Простыня текста на последних шести строчках называется трэйсбэком. Он выглядит страшно, но читать его — полезно. В нём Python рассказывает что же случилось.
Важно: трейсбек читают снизу вверх. Именно поэтому мы расставили такую нумерацию строчек в примере.
Давайте разберём каждую строчку в примере выше:
1) ZeroDivisionError: division by zero
— тип исключения и сообщение с причиной ошибки. Если перевести на русский, сразу понятно: ОшибкаДеленияНаНоль: вы поделили на ноль.
2) return sum(numbers) / len(numbers)
— строка, на которой произошла ошибка.
3) File "example.py", line 2, in average
— где эту строку искать. В файле example.py
, на второй строке, в функции average
.
4 и 5) Начиная с этой строки и далее, Python будет указывать какие строки кода исполнялись до момента, с которого началась вся цепочка. Эта информация нужна, когда ошибка произошла где-то внутри чужого кода. Просматривая трейсбэк снизу вверх, можно найти строчку вашего кода, которая стала причиной ошибки.
6) Начало трейсбэка
Каким бы длинным трейсбек ни был, в большинстве случаев вам понадобится только последние две-три строки. Как правило, их достаточно, чтобы определить что и где произошло. Выходит, трейсбэки не такие уж и страшные, если знать, где искать.
Как перехватить ошибку
Если возникшее исключение — часть плана или вы хотите обработать его особенном образом, то на такой случай в Python существует конструкция try-except
:
filepath = user.get_avatar_filepath()
try:
avatar = Image.open(filepath)
except FileNotFoundError:
avatar = default_avatar
Внутри блока try
(внутри — это с отступами) пишется код, который потенциально может вызвать ошибку. Если исключения не произойдёт, то Python проигнорирует блок except
и пойдёт дальше. Если же возникла ошибка — сработает код внутри блока except
.
Обратите внимание, что после except
стоит тип исключения, который может случиться внутри try
. Это правило хорошего тона. Мы явно указываем тип ошибки, которую ожидаем.
Код, в котором не указан тип ошибки выглядит так:
filepath = user.get_avatar_filepath()
try:
avatar = use_avatar(filepath)
except:
avatar = default_avatar
Однажды может случится страшное: другая ошибка. Но код будет себя вести так, будто ничего страшного не произошло, будто он просто не нашёл файл. Python не покажет трейсбек и чинить код придется методом ненаучного тыка.
Когда разработчик не указывает тип исключения, кажется, будто он просто написал такой плохой код, что сам себе не доверяет. Без особой необходимости так делать нельзя.
Что ещё почитать
- Где обработать исключение
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.