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.
La réponse acceptée mérite d'être acceptée et répond à la question, j'écris ceci uniquement pour fournir un peu de contexte supplémentaire.
Un des credos de Python est: il est plus facile de demander pardon que permission. Cela signifie qu'en général, vous ne faites que des choses et si vous attendez des exceptions, vous les gérez. Par opposition à faire si vérifie à l'avance pour vous assurer que vous n'obtiendrez pas d'exception.
Je veux fournir un exemple pour vous montrer à quel point la différence de mentalité avec C ++ / Java est dramatique. Une boucle for en C ++ ressemble généralement à quelque chose comme:
Une façon de penser à cela: accéder à
myvector[k]
où k> = myvector.size () provoquera une exception. Donc, vous pourriez en principe écrire cela (très maladroitement) comme un try-catch.Ou quelque chose de similaire. Maintenant, considérez ce qui se passe dans une boucle python for:
Comment ça marche? La boucle for prend le résultat de range (1) et appelle iter () dessus, saisissant un itérateur.
Ensuite, il appelle ensuite à chaque itération de boucle, jusqu'à ce que ...:
En d'autres termes, une boucle for en python est en fait un essai, sauf déguisé.
En ce qui concerne la question concrète, rappelez-vous que les exceptions arrêtent l'exécution normale des fonctions et doivent être traitées séparément. En Python, vous devez les lancer librement chaque fois qu'il n'y a aucun point à exécuter le reste du code dans votre fonction, et / ou qu'aucun des retours ne reflète correctement ce qui s'est passé dans la fonction. Notez que retourner tôt à partir d'une fonction est différent: retourner tôt signifie que vous avez déjà compris la réponse et n'avez pas besoin du reste du code pour comprendre la réponse. Je dis que des exceptions doivent être levées lorsque la réponse n'est pas connue, et le reste du code pour déterminer la réponse ne peut pas être raisonnablement exécuté. Maintenant, "refléter correctement" lui-même, comme les exceptions que vous choisissez de lever, est une question de documentation.
Dans le cas de votre code particulier, je dirais que toute situation qui fait que les hits soient une liste vide devrait être levée. Pourquoi? Eh bien, la façon dont votre fonction est configurée, il n'y a aucun moyen de déterminer la réponse sans analyser les hits. Donc, si les hits ne sont pas analysables, soit parce que l'URL est mauvaise, soit parce que les hits sont vides, alors la fonction ne peut pas répondre à la question, et en fait ne peut même pas vraiment essayer.
Dans ce cas particulier, je dirais que même si vous parvenez à analyser et n'obtenez pas de réponse raisonnable (vivante ou morte), vous devez quand même lancer. Pourquoi? Parce que la fonction renvoie un booléen. Aucun retour n'est très dangereux pour votre client. S'ils effectuent une vérification if sur Aucun, il n'y aura pas d'échec, il sera simplement traité en silence comme Faux. Ainsi, votre client devra fondamentalement toujours faire un test if is None de toute façon s'il ne veut pas d'échecs silencieux ... vous devriez donc probablement simplement jeter.
la source
Vous devez utiliser des exceptions lorsque quelque chose d' exceptionnel se produit. Autrement dit, quelque chose qui ne devrait pas se produire étant donné l'utilisation appropriée de l'application. S'il est permis et attendu que le consommateur de votre méthode recherche quelque chose qui ne sera pas trouvé, alors "non trouvé" n'est pas un cas exceptionnel. Dans ce cas, vous devez retourner null ou "None" ou {}, ou quelque chose indiquant un ensemble de retours vide.
Si, d'autre part, vous vous attendez vraiment à ce que les consommateurs de votre méthode trouvent toujours (à moins qu'ils aient foiré d'une manière ou d'une autre) ce qui est recherché, alors ne pas le trouver serait une exception et vous devriez y aller.
La clé est que la gestion des exceptions peut être coûteuse - les exceptions sont censées recueillir des informations sur l'état de votre application lorsqu'elles se produisent, comme une trace de pile, afin d'aider les gens à déchiffrer pourquoi elles se sont produites. Je ne pense pas que c'est ce que vous essayez de faire.
la source
String
et que vous choisissez "Aucun" comme indicateur, cela signifie que vous devez faire attention à ce que "Aucun" ne soit jamais une valeur valide. Notez également qu'il y a une différence entre regarder les données et ne pas trouver de valeur et ne pas pouvoir récupérer les données, donc nous ne pouvons pas trouver les données. Avoir le même résultat pour ces deux cas signifie que vous n'avez aucune visibilité une fois que vous n'obtenez aucune valeur lorsque vous vous attendez à ce qu'il y en ait un.Si j'écrivais une fonction
Je l'écrirais dans
return True
ouFalse
dans les cas où je suis absolument certain de l'un ou de l'autre, et d'raise
une erreur dans tout autre cas (par exempleraise ValueError("Status neither 'dead' nor 'alive'")
). C'est parce que la fonction qui appelle la mienne attend un booléen, et si je ne peux pas fournir cela avec certitude, le flux de programme normal ne devrait pas continuer.Quelque chose comme votre exemple d'obtenir un nombre de "hits" différent de celui attendu, je l'ignorerais probablement; tant que l'un des hits correspond toujours à mon schéma "Abe Vigoda est {mort | vivant}", c'est très bien. Cela permet de réorganiser la page mais obtient toujours les informations appropriées.
Plutôt que
Je vérifierais explicitement:
car cela a tendance à être «moins cher», puis à configurer le
try
.Je suis d'accord avec vous
IOError
; Je n'essaierais pas non plus de gérer la connexion au site Web - si nous ne pouvons pas, pour une raison quelconque, ce n'est pas l'endroit approprié pour le gérer (car cela ne nous aide pas à répondre à notre question) et cela devrait passer vers la fonction appelante.la source