Избегание частых ошибок: правильная обработка исключений в Python
Я вижу много людей, обрабатывающих исключения неправильно. Возможно, это относится и к вам. Знакома ли следующая ситуация?
Вы пишете какой-то код, но знаете, что используемая библиотека может поднять исключение. Вы не помните, точно какое. В этот момент соблазн использовать так называемые catch-all блоки и перейти к веселым вещам.
Содержание
Худший способ сделать это
Худшее, что вы можете сделать — создать try-except блок, который ловит всё. Под catch-all я имею в виду что-то вроде:
try: ... except: pass
Catch-all блоки вроде этих плохи, потому что:
- Вы не имеете представления, какие еще исключения могут быть подняты (об этом позже).
- Мы прячем исключение, молча используя pass вместо логирования ошибки.
Кроме того, пустой except будет ловить всё, включая KeyboardInterrupt (Ctrl+C), SystemExit и даже NameError! Это значит, что следующий код не может быть остановлен чисто:
from time import sleep
while True:
try:
print("Try and stop me")
sleep(1)
except:
print("Don't.. stop.. me now!")
Не стесняйтесь попробовать. Вам нужно закрыть окно терминала или убить Python-процесс, чтобы остановить эту программу.
Некоторое лучший способ поймать все исключения
В противовес, когда используется except Exception, хотя это все еще быстрый и грязный способ поймать слишком много исключений, по крайней мере вы сможете правильно остановить запущенный процесс:
from time import sleep
while True:
try:
print("Try and stop me")
sleep(1)
except Exception:
print("Ok I'll stop!")
Когда ловите Exception, вы не ловите SystemExit, KeyboardInterrupt и другие такие исключения. Почему так, спрашиваете вы?
Все исключения наследуются от класса BaseException. Согласно официальной документации: «В try выражении с except клаузой, упоминающей конкретный класс, эта клауза также обрабатывает любые классы исключений, производные от этого класса». Пустой except эквивалентен except BaseException, поэтому он ловит все возможные исключения.
В противовес, класс Exception определен как: «Все встроенные, не системные исключения наследуются от этого класса. Все пользовательские исключения также должны наследоваться от этого класса».
Становится еще хуже
В следующем примере мы используем библиотеку os для получения текущей рабочей директории. Однако мои толстые пальцы сделали опечатку:
import os
try:
working_dir = os.getcdw()
print(working_dir)
except:
print('error')
Потому что os.getcdw не является функцией в модуле os, выбрасывается NameError. Вместо падения, except-клауза ловит ошибку, печатает 'error' и программа продолжается несмотря на нашу явную опечатку. К сожалению, это не решается ловлей Exception тоже!
Видимо, наш маленький трюк из шага первого не исправляет все наши проблемы. Так что должны мы делать?
Ловите то, что можете обработать
Фраза, которую часто слышат об исключениях: ловите то, что можете обработать. Многие разработчики соблазнены прямо иметь дело с исключениями, в то время как часто лучше дать исключение всплыть в часть вашей программы, которая может его фактически обработать.
Например, рассмотрите часть текстового редактора, которая открывает и загружает файлы, назовем её класс OpenFile. Если пользователь запросил открыть файл, который не существует, вы можете либо напрямую обработать эту ошибку, либо дать ей всплыть.
В этом случае лучше передать исключение вызывающему, потому что OpenFile не знает, насколько плохо это исключение для вызывающего. Вызывающий может обработать ситуацию множеством способов:
- Он может создать новый файл с этим именем вместо этого и продолжить
- Может быть, вызывающему нужен файл там, в этом случае он может показать диалог ошибки, информируя пользователя, что этот файл не существует.
В любом случае, не за OpenFile классом решать, что делать в случае FileNotFoundError.
Должно ли исключение всегда всплывать? Нет. Возможное исключение, которое может быть обработано в классе FileOpen — это TimeoutError. Вы можете захотеть повторить несколько раз, например, не беспокоя вызывающего об ошибке. Это исключение, которое OpenFile может обработать, поэтому нормально его ловить и повторять попытку.
Заключение
Вы ни при каких обстоятельствах не должны ловить больше исключений, чем можете обработать. Пустые except-блоки — рецепт для багов и непредсказуемого кода. Другими словами: ловите то, что можете обработать.
Если вы пишете код с мантрой «ловите то, что можете обработать» в уме, написание catch-all блоков нарушает все правила. Так что, пожалуйста, перестаньте это делать. Как упражнение, вы могли бы пересмотреть некоторые из вашего существующего кода и увидеть, может ли он быть улучшен с этими новыми знаниями!
Python
- Мастерство операторов Python: Полное руководство
- Ошибки и встроенные исключения в Python: полное руководство
- Пользовательские исключения в Python: практическое руководство
- Получение текущей даты и времени в Python: практическое руководство
- Мульти-исключения в Java: перехват нескольких исключений в одном catch блоке
- Мастерство обучения CMMS: вовлечение команд и повышение удержания
- Функция print() в Python: практическое руководство с примерами
- Словари Python: добавление, обновление и управление парами ключ/значение
- Печать в Python без перевода строки: мастерство параметра end и другие техники
- Вычисление среднего значения в Python: практическое руководство