Le réglage
J'ai souvent du mal à déterminer quand et comment utiliser les exceptions. Prenons un exemple simple: supposons que je racle une page Web, dites " http://www.abevigoda.com/ ", pour déterminer si Abe Vigoda est toujours en vie. Pour ce faire, il suffit de télécharger la page et de rechercher les moments où l'expression "Abe Vigoda" apparaît. Nous rendons la première apparition, car cela inclut le statut d'Abe. Conceptuellement, cela ressemblera à ceci:
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"
Où parse_abe_status(s)
prend une chaîne de la forme "Abe Vigoda est quelque chose " et renvoie la partie " quelque chose ".
Avant de prétendre qu'il existe des façons bien meilleures et plus robustes de gratter cette page pour le statut d'Abe, rappelez-vous que ce n'est qu'un exemple simple et artificiel utilisé pour mettre en évidence une situation courante dans laquelle je me trouve.
Maintenant, où ce code peut-il rencontrer des problèmes? Parmi d'autres erreurs, certaines "attendues" sont:
download_page
peut ne pas être en mesure de télécharger la page et lance unIOError
.- L'URL peut ne pas pointer vers la bonne page ou la page est téléchargée de manière incorrecte et il n'y a donc pas de résultats.
hits
est la liste vide, alors. - La page Web a été modifiée, ce qui rend peut-être fausses nos hypothèses sur la page. Peut-être que nous attendons 4 mentions d'Abe Vigoda, mais maintenant nous en trouvons 5.
- Pour certaines raisons, il
hits[0]
peut ne pas s'agir d'une chaîne de la forme "Abe Vigoda est quelque chose ", et elle ne peut donc pas être correctement analysée.
Le premier cas n'est pas vraiment un problème pour moi: un IOError
est lancé et peut être géré par l'appelant de ma fonction. Examinons donc les autres cas et comment je pourrais les gérer. Mais d'abord, supposons que nous implémentions parse_abe_status
de la manière la plus stupide possible:
def parse_abe_status(s):
return s[13:]
À savoir, il ne fait aucune vérification d'erreur. Maintenant, passons aux options:
Option 1: retour None
Je peux dire à l'appelant que quelque chose s'est mal passé en retournant 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"
Si l'appelant reçoit None
de ma fonction, il doit supposer qu'il n'y a pas eu de mention d'Abe Vigoda, et donc quelque chose s'est mal passé. Mais c'est assez vague, non? Et cela n'aide pas le cas où ce hits[0]
n'est pas ce que nous pensions que c'était.
En revanche, nous pouvons prévoir quelques exceptions:
Option 2: utilisation d'exceptions
Si hits
est vide, un IndexError
sera lancé lorsque nous tenterons hits[0]
. Mais il ne faut pas s'attendre à ce que l'appelant gère une IndexError
projection de ma fonction, car il n'a aucune idée d'où cela IndexError
vient; il aurait pu être jeté par find_all_mentions
, pour tout ce qu'il sait. Nous allons donc créer une classe d'exception personnalisée pour gérer cela:
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"
Maintenant, que se passe-t-il si la page a changé et qu'il y a un nombre inattendu de visites? Ce n'est pas catastrophique, car le code peut encore fonctionner, mais un appelant peut vouloir être supplémentaire prudent, ou il pourrait vouloir connecter un avertissement. Je vais donc lancer un avertissement:
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"
Enfin, nous pourrions trouver que ce status
n'est ni vivant ni mort. Peut-être, pour une raison étrange, aujourd'hui, cela s'est avéré être comatose
. Ensuite, je ne veux pas revenir False
, car cela implique qu'Abe est mort. Que dois-je faire ici? Jetez une exception, probablement. Mais quel genre? Dois-je créer une classe d'exception personnalisée?
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"
Option 3: quelque part entre les deux
Je pense que la deuxième méthode, avec des exceptions, est préférable, mais je ne sais pas si j'utilise correctement les exceptions. Je suis curieux de voir comment des programmeurs plus expérimentés géreraient cela.