Ustawienie
Często mam problemy z określeniem, kiedy i jak korzystać z wyjątków. Rozważmy prosty przykład: załóżmy, że przeglądam stronę internetową, powiedz „ http://www.abevigoda.com/ ”, aby ustalić, czy Abe Vigoda nadal żyje. Aby to zrobić, wystarczy pobrać stronę i poszukać czasów, w których pojawia się zwrot „Abe Vigoda”. Zwracamy pierwszy występ, ponieważ obejmuje to status Abe. Koncepcyjnie będzie to wyglądać tak:
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"
Gdzie parse_abe_status(s)
przyjmuje ciąg formy „Abe Vigoda jest czymś ” i zwraca część „ coś ”.
Zanim przekonasz się, że istnieją znacznie lepsze i bardziej niezawodne sposoby skrobania tej strony w celu uzyskania statusu Abe, pamiętaj, że jest to prosty i przemyślany przykład użyty do podkreślenia typowej sytuacji, w której się znajduję.
Gdzie ten kod może napotykać problemy? Wśród innych błędów niektóre „oczekiwane” to:
download_page
może nie być w stanie pobrać strony i zgłaszaIOError
.- Adres URL może nie wskazywać właściwej strony lub strona jest niepoprawnie pobrana, więc nie ma żadnych trafień.
hits
jest więc pusta lista. - Strona internetowa została zmieniona, co prawdopodobnie czyni nasze założenia dotyczące strony błędnymi. Może oczekujemy 4 wzmianek o Abe Vigodzie, ale teraz znajdujemy 5.
- Z niektórych powodów
hits[0]
może nie być ciągiem w formie „Abe Vigoda jest czymś ”, więc nie można go poprawnie przeanalizować.
Pierwszy przypadek nie jest dla mnie problemem: an IOError
jest rzucany i może być obsłużony przez program wywołujący moją funkcję. Zastanówmy się więc nad innymi przypadkami i jak sobie z nimi poradzić. Ale najpierw załóżmy, że wdrażamy parse_abe_status
w najgłupszy możliwy sposób:
def parse_abe_status(s):
return s[13:]
Mianowicie nie sprawdza błędów. Teraz przejdź do opcji:
Opcja 1: powrót None
Mogę powiedzieć dzwoniącemu, że coś poszło nie tak, zwracając 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"
Jeśli dzwoniący otrzymuje None
od mojej funkcji, powinien założyć, że nie było wzmianek o Abe Vigodzie, a więc coś poszło nie tak. Ale to dość niejasne, prawda? I to nie pomaga w przypadku, gdy hits[0]
nie jest tak, jak nam się wydawało.
Z drugiej strony możemy wprowadzić pewne wyjątki:
Opcja 2: Korzystanie z wyjątków
Jeśli hits
jest pusty, IndexError
zostanie rzucony podczas próby hits[0]
. Ale nie należy oczekiwać, że osoba dzwoniąca poradzi sobie z IndexError
rzuconą przez moją funkcję, ponieważ nie ma pojęcia, skąd ona IndexError
pochodzi; mogło to zostać zrzucone find_all_mentions
, o ile on wie. Dlatego stworzymy niestandardową klasę wyjątków, aby obsłużyć to:
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"
Co się stanie, jeśli strona ulegnie zmianie i pojawi się nieoczekiwana liczba wyświetleń? Nie jest to katastrofalne, ponieważ kod może nadal działać, ale osoba dzwoniąca może chcieć być bardzo ostrożna lub może zarejestrować ostrzeżenie. Więc rzucę ostrzeżenie:
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"
Wreszcie możemy odkryć, że status
nie jest ani żywy, ani martwy. Być może z jakiegoś dziwnego powodu dziś tak się stało comatose
. Więc nie chcę wracać False
, ponieważ to sugeruje, że Abe nie żyje. Co mam tu zrobić? Prawdopodobnie rzuć wyjątek. Ale jaki? Czy powinienem utworzyć niestandardową klasę wyjątków?
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"
Opcja 3: Gdzieś pomiędzy
Myślę, że druga metoda, z wyjątkami, jest lepsza, ale nie jestem pewien, czy prawidłowo używam wyjątków. Jestem ciekawy, jak poradzą sobie z tym bardziej doświadczeni programiści.