Настройка
Мне часто бывает трудно определить, когда и как использовать исключения. Давайте рассмотрим простой пример: предположим, я зачищаю веб-страницу, скажем « 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: где-то посередине
Я думаю, что второй метод, за исключением, предпочтительнее, но я не уверен, правильно ли я использую исключения в нем. Мне любопытно посмотреть, как более опытные программисты справятся с этим.
источник
Принятый ответ заслуживает принятия и отвечает на вопрос, я пишу это только для того, чтобы предоставить немного дополнительной информации.
Одно из кредо Python: проще просить прощения, чем разрешения. Это означает, что обычно вы просто делаете что-то, и если вы ожидаете исключения, вы их обрабатываете. В отличие от выполнения проверок перед тем, как убедиться, что вы не получите исключение.
Я хочу привести пример, чтобы показать вам, насколько драматически отличается менталитет от C ++ / Java. Цикл for в C ++ обычно выглядит примерно так:
Можно подумать об этом: доступ к
myvector[k]
где k> = myvector.size () вызовет исключение. Таким образом, вы можете в принципе написать это (очень неловко) в качестве пробной уловки.Или что-то подобное. Теперь рассмотрим, что происходит в цикле python for:
Как это работает? Цикл for берет результат range (1) и вызывает iter (), захватывая итератор.
Затем он вызывает следующий на каждой итерации цикла, пока ...:
Другими словами, цикл for в python - это на самом деле попытка, за исключением маскировки.
Что касается конкретного вопроса, помните, что исключения останавливают нормальное выполнение функции и должны рассматриваться отдельно. В Python вы должны свободно генерировать их всякий раз, когда нет смысла выполнять остальную часть кода в вашей функции, и / или ни один из возвратов не отражает правильно то, что произошло в функции. Обратите внимание, что ранний возврат из функции отличается: возврат рано означает, что вы уже выяснили ответ и вам не нужен остальной код, чтобы выяснить ответ. Я говорю, что исключения должны создаваться, когда ответ неизвестен, а остальная часть кода для определения ответа не может быть разумно выполнена. Теперь «правильно отразить» себя, например, какие исключения вы выбрасываете - все это вопрос документации.
В случае вашего конкретного кода, я бы сказал, что любая ситуация, которая приводит к тому, что попадания будут пустым списком, должна генерироваться. Почему? Что ж, как настроена ваша функция, нет способа определить ответ без разбора хитов. Таким образом, если попадания не разбираются, либо потому, что URL-адрес плохой, либо потому что совпадения пустые, то функция не может ответить на вопрос и даже не может даже попытаться.
В этом конкретном случае я бы сказал, что даже если вам удастся разобрать и не получить разумного ответа (живого или мертвого), вам все равно следует бросить. Почему? Потому что функция возвращает логическое значение. Возвращение Ни один очень опасно для вашего клиента. Если они выполнят проверку «Нет», сбоя не будет, он будет просто рассматриваться как Ложный. Итак, ваш клиент в основном всегда должен делать проверку if is None, если он не хочет молчаливых сбоев ... так что вы, вероятно, должны просто бросить.
источник
Вы должны использовать исключения, когда происходит что-то исключительное . То есть то, что не должно происходить при правильном использовании приложения. Если для потребителя вашего метода допустимо и ожидается, что пользователь найдет что-то, что не будет найдено, то «not found» не является исключительным случаем. В этом случае вы должны вернуть null или «None» или {}, или что-то, указывающее пустой набор возврата.
Если, с другой стороны, вы действительно ожидаете, что потребители вашего метода всегда (если они не облажались) найдут то, что искали, тогда не обнаружение этого будет исключением, и вы должны пойти на это.
Ключевым моментом является то, что обработка исключений может быть дорогой - исключения должны собирать информацию о состоянии вашего приложения, когда они происходят, например трассировку стека, чтобы помочь людям понять, почему они произошли. Я не думаю, что это то, что вы пытаетесь сделать.
источник
String
и вы выбираете «None» в качестве индикатора, это означает, что вы должны быть осторожны, чтобы «None» никогда не было действительным значением. Также обратите внимание на то, что существует разница между просмотром данных и невозможностью найти значение и невозможностью извлечь данные, поэтому мы не можем найти данные. Наличие одинакового результата для этих двух случаев означает, что у вас нет никакой видимости, когда вы не получаете никакой ценности, когда ожидаете, что она будет.Если бы я писал функцию
Я написал бы это
return True
илиFalse
в случаях, когда я абсолютно уверен в одном или другом, иraise
ошибка в любом другом случае (напримерraise ValueError("Status neither 'dead' nor 'alive'")
). Это потому, что функция, вызывающая мою, ожидает логического значения, и если я не могу с уверенностью предоставить это, поток обычной программы не должен продолжаться.Что-то вроде вашего примера получения количества «хитов», отличного от ожидаемого, я бы, вероятно, проигнорировал; до тех пор, пока один из хитов по-прежнему соответствует моему шаблону "Abe Vigoda is {dead | alive}", это нормально. Это позволяет перестроить страницу, но все равно получает соответствующую информацию.
Скорее, чем
Я бы проверил явно:
так как это, как правило, «дешевле», чем настройка
try
.Я согласен с вами
IOError
; Я также не стал бы пытаться обработать ошибку при подключении к веб-сайту - если мы не можем по какой-то причине это не подходящее место для его обработки (поскольку это не помогает нам ответить на наш вопрос), и оно должно пройти к вызывающей функции.источник