Настройка
Мне часто бывает трудно определить, когда и как использовать исключения. Давайте рассмотрим простой пример: предположим, я зачищаю веб-страницу, скажем « http://www.abevigoda.com/ », чтобы определить, жива ли еще Абе Вигода. Для этого все, что нам нужно сделать, это загрузить страницу и посмотреть, когда появляется фраза «Abe Vigoda». Мы возвращаем первое появление, так как это включает статус Абэ. Концептуально это будет выглядеть так:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Где parse_abe_status(s)
берет строку вида «Abe Vigoda is нечто » и возвращает часть « что-то ».
Прежде чем утверждать, что есть гораздо лучшие и надежные способы очистки этой страницы для получения статуса Абэ, помните, что это простой и надуманный пример, используемый для выделения общей ситуации, в которой я нахожусь.
Теперь, где этот код может столкнуться с проблемами? Среди других ошибок, некоторые «ожидаемые»:
download_page
может быть не в состоянии загрузить страницу, и выдаетIOError
.- URL-адрес может не указывать на нужную страницу, или страница загружена неправильно, и поэтому нет обращений.
hits
это пустой список, то. - Веб-страница была изменена, возможно, наши предположения о странице неверны. Может быть, мы ожидаем 4 упоминания об Abe Vigoda, но теперь мы находим 5.
- По некоторым причинам
hits[0]
может не быть строки вида «Abe Vigoda - это нечто », и поэтому она не может быть правильно проанализирована.
Первый случай на самом деле не проблема для меня: IOError
он брошен и может быть обработан вызывающей стороной моей функции. Итак, давайте рассмотрим другие случаи и как я мог бы справиться с ними. Но сначала давайте предположим, что мы реализуем parse_abe_status
самым глупым способом:
def parse_abe_status(s):
return s[13:]
А именно, это не делает никакой проверки ошибок. Теперь перейдем к вариантам:
Вариант 1: Возврат None
Я могу сказать звонящему, что что-то пошло не так, вернув None
:
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
if not hits:
return None
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Если звонящий получает None
от моей функции, он должен предположить, что не было упоминаний об Абэ Вигоде, и что- то пошло не так. Но это довольно расплывчато, верно? И это не помогает случаю, когда hits[0]
это не то, что мы думали.
С другой стороны, мы можем сделать несколько исключений:
Вариант 2. Использование исключений
Если hits
пусто, IndexError
будет брошено, когда мы попытаемся hits[0]
. Но нельзя ожидать, что вызывающий вызов будет обрабатывать IndexError
вызов, выполняемый моей функцией, поскольку он понятия не имеет, откуда это IndexError
произошло; это, возможно, было брошено find_all_mentions
, насколько он знает. Поэтому мы создадим собственный класс исключений для обработки этого:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Что теперь, если страница изменилась и было неожиданное количество просмотров? Это не катастрофично, так как код все еще может работать, но вызывающий может захотеть быть очень осторожным или записать предупреждение. Поэтому я брошу предупреждение:
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
# he's either alive or dead
return status == "alive"
Наконец, мы можем обнаружить, что status
он не жив и не мертв. Может быть, по какой-то странной причине сегодня так и случилось comatose
. Тогда я не хочу возвращаться False
, поскольку это означает, что Эйб мертв. Что мне здесь делать? Брось исключение, наверное. Но что это за вид? Должен ли я создать собственный класс исключений?
class NotFoundError(Exception):
"""Throw this when something can't be found on a page."""
def get_abe_status(url):
# download the page
page = download_page(url)
# get all mentions of Abe Vigoda
hits = page.find_all_mentions("Abe Vigoda")
try:
hits[0]
except IndexError:
raise NotFoundError("No mentions found.")
# say we expect four hits...
if len(hits) != 4:
raise Warning("An unexpected number of hits.")
logger.warning("An unexpected number of hits.")
# parse the first hit for his status
status = parse_abe_status(hits[0])
if status not in ['alive', 'dead']:
raise SomeTypeOfError("Status is an unexpected value.")
# he's either alive or dead
return status == "alive"
Вариант 3: где-то посередине
Я думаю, что второй метод, за исключением, предпочтительнее, но я не уверен, правильно ли я использую исключения в нем. Мне любопытно посмотреть, как более опытные программисты справятся с этим.