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.
źródło
Przyjęta odpowiedź zasługuje na akceptację i odpowiada na pytanie, piszę to tylko po to, aby zapewnić trochę dodatkowego tła.
Jednym z credo Pythona jest: łatwiej prosić o wybaczenie niż o pozwolenie. Oznacza to, że zazwyczaj po prostu robisz różne rzeczy, a jeśli oczekujesz wyjątków, radzisz sobie z nimi. W przeciwieństwie do robienia czeków przed ręką, aby upewnić się, że nie otrzymasz wyjątku.
Chcę podać przykład pokazujący, jak dramatyczna jest różnica w mentalności od C ++ / Java. Pętla for w C ++ zwykle wygląda mniej więcej tak:
Sposób na przemyślenie tego: dostęp do miejsca, w
myvector[k]
którym k> = myvector.size () spowoduje wyjątek. Możesz więc w zasadzie napisać to (bardzo niezręcznie) jako próbę.Lub coś podobnego. Teraz zastanów się, co się dzieje w pętli python for:
Jak to działa? Pętla for bierze wynik z zakresu (1) i wywołuje iter (), chwytając do niego iterator.
Następnie wywołuje go przy każdej iteracji pętli, dopóki ...:
Innymi słowy, pętla for w pythonie jest tak naprawdę próbą-z wyjątkiem w przebraniu.
Jeśli chodzi o konkretne pytanie, pamiętaj, że wyjątki zatrzymują normalne wykonywanie funkcji i należy je rozwiązać osobno. W Pythonie powinieneś swobodnie je rzucać, ilekroć nie ma sensu wykonywać reszty kodu w twojej funkcji i / lub żaden zwrot nie poprawnie odzwierciedla tego, co się stało w funkcji. Zauważ, że wczesne wracanie z funkcji jest inne: wczesne zwracanie oznacza, że już wymyśliłeś odpowiedź i nie potrzebujesz reszty kodu, aby znaleźć odpowiedź. Mówię, że wyjątki należy zgłaszać, gdy odpowiedź nie jest znana, a reszty kodu określającego odpowiedź nie można rozsądnie uruchomić. Teraz „prawidłowe odzwierciedlenie” samego siebie, podobnie jak to, które wyjątki wybierzesz, jest kwestią dokumentacji.
W przypadku twojego konkretnego kodu powiedziałbym, że każda sytuacja, która powoduje, że trafienia są pustą listą, powinna rzucić. Dlaczego? Cóż, sposób, w jaki twoja funkcja jest skonfigurowana, nie ma sposobu na określenie odpowiedzi bez analizowania trafień. Więc jeśli trafień nie można przeanalizować, albo dlatego, że adres URL jest zły, albo dlatego, że trafienia są puste, funkcja nie może odpowiedzieć na pytanie, a tak naprawdę nie może nawet próbować.
W tym konkretnym przypadku argumentowałbym, że nawet jeśli uda ci się przeanalizować i nie uzyskasz rozsądnej odpowiedzi (żywej lub martwej), nadal powinieneś rzucić. Dlaczego? Ponieważ funkcja zwraca wartość logiczną. Zwrot None Brak jest bardzo niebezpieczny dla Twojego klienta. Jeśli sprawdzą, czy nie ma opcji Brak, nie wystąpi awaria, będzie ona po prostu cicho traktowana jako Fałsz. Tak więc twój klient w zasadzie zawsze będzie musiał sprawdzić, czy „None is None” i czy nie chce cichych awarii… więc prawdopodobnie powinieneś po prostu rzucić.
źródło
Powinieneś stosować wyjątki, gdy dzieje się coś wyjątkowego . Oznacza to, że coś nie powinno się zdarzyć przy właściwym użyciu aplikacji. Jeśli konsument Twojej metody może i szuka czegoś, co nie zostanie znalezione, wówczas „nie znaleziono” nie jest wyjątkowym przypadkiem. W takim przypadku powinieneś zwrócić null lub „None” lub {} albo coś wskazującego na pusty zestaw zwrotów.
Z drugiej strony, jeśli naprawdę oczekujesz, że konsumenci twojej metody zawsze (chyba że jakoś spieprzą) znajdą to, co jest wyszukiwane, to nie znalezienie tego byłoby wyjątkiem i powinieneś to zrobić.
Kluczem jest to, że obsługa wyjątków może być kosztowna - wyjątki mają na celu zebranie informacji o stanie twojej aplikacji w momencie ich wystąpienia, takich jak ślad stosu, aby pomóc ludziom w odszyfrowaniu przyczyny ich wystąpienia. Nie sądzę, że to właśnie próbujesz zrobić.
źródło
String
a jako wskaźnik wybrałeś „Brak”, oznacza to, że musisz uważać, aby „Brak” nigdy nie był prawidłową wartością. Zauważ też, że istnieje różnica między spojrzeniem na dane a nie znalezieniem wartości i niemożnością odzyskania danych, dlatego nie możemy znaleźć danych. Osiągnięcie tego samego wyniku dla tych dwóch przypadków oznacza, że nie masz widoczności, gdy nie otrzymujesz żadnej wartości, gdy spodziewasz się, że będzie.Gdybym pisał funkcję
Napiszę to do
return True
lubFalse
w przypadkach, w których jestem absolutnie pewien jednego lub drugiego, araise
błąd w każdym innym przypadku (npraise ValueError("Status neither 'dead' nor 'alive'")
.). Wynika to z faktu, że funkcja wywołująca mój oczekuje wartości logicznej, a jeśli nie mogę zapewnić tego z pewnością, zwykły przepływ programu nie powinien być kontynuowany.Coś w rodzaju twojego przykładu uzyskania innej liczby „trafień” niż oczekiwano, prawdopodobnie zignoruję; dopóki jeden z hitów nadal pasuje do mojego wzoru „Abe Vigoda jest {dead | alive}”, to w porządku. Pozwala to na zmianę kolejności strony, ale nadal otrzymuje odpowiednie informacje.
Zamiast
Chciałbym wyraźnie sprawdzić:
ponieważ jest to zwykle „tańsze”, niż konfiguracja
try
.Zgadzam się z tobą
IOError
; Nie starałbym się również błędnie obsługiwać łączenia ze stroną internetową - jeśli z jakiegoś powodu nie możemy, to nie jest odpowiednie miejsce do obsługi tego (ponieważ nie pomaga nam to odpowiedzieć na nasze pytanie) i powinno przejść do funkcji wywoływania.źródło