L'impostazione
Spesso ho problemi a determinare quando e come usare le eccezioni. Consideriamo un semplice esempio: supponiamo che io scriva una pagina web, dica " link ", per determinare se Abe Vigoda è ancora vivo. Per fare questo, tutto ciò che dobbiamo fare è scaricare la pagina e cercare le volte che appare la frase "Abe Vigoda". Restituiamo la prima apparizione, dal momento che include lo stato di Abe. Concettualmente, assomiglierà a questo:
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"
Dove parse_abe_status(s)
prende una stringa del modulo "Abe Vigoda è qualcosa " e restituisce la parte " qualcosa ".
Prima di argomentare che ci sono modi migliori e più robusti per raschiare questa pagina per lo stato di Abe, ricorda che questo è solo un esempio semplice e forzato usato per evidenziare una situazione comune in cui mi trovo.
Ora, dove questo codice può incontrare dei problemi? Tra gli altri errori, alcuni "previsti" sono:
-
download_page
potrebbe non essere in grado di scaricare la pagina e lancia unIOError
. - L'URL potrebbe non puntare alla pagina giusta, oppure la pagina viene scaricata in modo errato e quindi non ci sono hit.
hits
è la lista vuota, quindi. - La pagina web è stata alterata, forse rendendo errate le nostre supposizioni sulla pagina. Forse ci aspettiamo 4 menzioni di Abe Vigoda, ma ora troviamo 5.
- Per alcuni motivi,
hits[0]
potrebbe non essere una stringa del modulo "Abe Vigoda è qualcosa " e quindi non può essere analizzato correttamente.
Il primo caso non è davvero un problema per me: viene generata una IOError
e può essere gestita dal chiamante della mia funzione. Quindi consideriamo gli altri casi e come potrei gestirli. Ma prima, supponiamo di implementare parse_abe_status
nel modo più stupido possibile:
def parse_abe_status(s):
return s[13:]
Ovvero, non esegue alcun controllo degli errori. Ora, sulle opzioni:
Opzione 1: ritorno None
Posso dire al chiamante che qualcosa è andato storto restituendo 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"
Se il chiamante riceve None
dalla mia funzione, dovrebbe presumere che non ci fossero menzioni di Abe Vigoda, e quindi qualcosa è andato storto. Ma questo è abbastanza vago, giusto? E non aiuta il caso in cui hits[0]
non è quello che pensavamo fosse.
D'altra parte, possiamo aggiungere alcune eccezioni:
Opzione 2: utilizzo delle eccezioni
Se hits
è vuoto, verrà generata una IndexError
quando tentiamo hits[0]
. Ma non ci si deve aspettare che il chiamante gestisca un IndexError
generato dalla mia funzione, dal momento che non ha idea di dove sia venuto il IndexError
; potrebbe essere stato lanciato da find_all_mentions
, per quanto ne sa. Quindi creeremo una classe di eccezioni personalizzata per gestire questo:
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"
Ora cosa succede se la pagina è cambiata e c'è un numero imprevisto di colpi? Questo non è catastrofico, in quanto il codice potrebbe ancora funzionare, ma un chiamante potrebbe desiderare di essere extra attento, o potrebbe voler registrare un avviso. Quindi lancerò un avvertimento:
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"
Infine, potremmo scoprire che status
non è vivo o morto. Forse, per qualche strana ragione, oggi si è rivelato essere comatose
. Quindi non voglio restituire False
, poiché ciò implica che Abe è morto. Cosa dovrei fare qui? Fai un'eccezione, probabilmente. Ma che tipo? Devo creare una classe di eccezioni personalizzata?
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"
Opzione 3: Da qualche parte in mezzo
Penso che il secondo metodo, con le eccezioni, sia preferibile, ma non sono sicuro se sto usando eccezioni correttamente al suo interno. Sono curioso di vedere come i programmatori più esperti potrebbero gestire questo.