Установка
У мене часто виникають проблеми з визначенням, коли і як використовувати винятки. Розглянемо простий приклад: припустимо, я перебираю веб-сторінку, скажімо, " http://www.abevigoda.com/ ", щоб визначити, чи Abe Vigoda ще живий. Для цього все, що нам потрібно зробити, - це завантажити сторінку та шукати рази, коли з’явиться фраза «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 - це щось " і повертає частину " щось ".
Перш ніж стверджувати, що існують набагато кращі та надійніші способи скребки цієї сторінки на статус Абе, пам’ятайте, що це лише простий і надуманий приклад, який використовується для висвітлення загальної ситуації, в якій я перебуваю.
Тепер, де цей код може зіткнутися з проблемами? Серед інших помилок, деякі "очікувані" такі:
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: Десь посередині
Я думаю, що другий метод, за винятком, є кращим, але я не впевнений, чи правильно в ньому використовую винятки. Мені цікаво побачити, як досвідчені програмісти впораються з цим.