Le impostazioni
Spesso ho difficoltà a determinare quando e come utilizzare le eccezioni. Consideriamo un semplice esempio: supponiamo che stia raschiando una pagina Web, ad esempio " http://www.abevigoda.com/ ", per determinare se Abe Vigoda è ancora vivo. Per fare ciò, tutto ciò che dobbiamo fare è scaricare la pagina e cercare le volte in cui appare la frase "Abe Vigoda". Restituiamo la prima apparizione, poiché include lo status di Abe. Concettualmente, sarà simile 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 sostenere che ci sono modi molto migliori e più robusti per eliminare questa pagina per lo stato di Abe, ricorda che questo è solo un esempio semplice e inventato utilizzato per evidenziare una situazione comune in cui mi trovo.
Ora, dove possono verificarsi problemi questo codice? Tra gli altri errori, alcuni "previsti" sono:
download_page
potrebbe non essere in grado di scaricare la pagina e genera unIOError
.- L'URL potrebbe non puntare alla pagina giusta oppure la pagina viene scaricata in modo errato, quindi non ci sono hit.
hits
è l'elenco vuoto, quindi. - La pagina Web è stata modificata, probabilmente rendendo errati i nostri presupposti sulla pagina. Forse ci aspettiamo 4 menzioni di Abe Vigoda, ma ora ne 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: IOError
viene generato e può essere gestito dal chiamante della mia funzione. Quindi consideriamo gli altri casi e come potrei gestirli. Ma prima, supponiamo che implementiamo parse_abe_status
nel modo più stupido possibile:
def parse_abe_status(s):
return s[13:]
Vale a dire, non esegue alcun controllo degli errori. Ora, per le 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 siano menzioni di Abe Vigoda, e quindi qualcosa è andato storto. Ma questo è piuttosto vago, giusto? E non aiuta il caso in cui hits[0]
non è quello che pensavamo fosse.
D'altra parte, possiamo mettere alcune eccezioni:
Opzione 2: utilizzo delle eccezioni
Se hits
è vuoto, IndexError
verrà lanciato un testamento quando tentiamo hits[0]
. Ma non ci si dovrebbe aspettare che il chiamante gestisca una IndexError
mia funzione, dal momento che non ha idea di da dove IndexError
provenga; avrebbe potuto essere lanciato da find_all_mentions
, per quel che 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"
E se la pagina fosse cambiata e ci fosse un numero inaspettato di hit? Questo non è catastrofica, in quanto il codice può ancora funzionare, ma il chiamante potrebbe desiderare di essere più attenti, o lui potrebbe voler registrare un avvertimento. 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 è né vivo né morto. Forse, per qualche strana ragione, oggi si è rivelato essere comatose
. Quindi non voglio tornare False
, poiché ciò implica che Abe è morto. Cosa dovrei fare qui? Lancia un'eccezione, probabilmente. Ma che tipo? Devo creare una classe di eccezione 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 nel mezzo
Penso che il secondo metodo, con le eccezioni, sia preferibile, ma non sono sicuro di utilizzare correttamente le eccezioni al suo interno. Sono curioso di vedere come programmatori più esperti potrebbero gestire questo.