Mieux vaut «essayer» quelque chose et attraper l'exception ou tester s'il est d'abord possible d'éviter une exception?

139

Dois-je tester que ifquelque chose est valide ou simplement tryle faire et attraper l'exception?

  • Existe-t-il une documentation solide indiquant qu'une solution est préférable?
  • Est-ce qu'une manière est plus pythonique ?

Par exemple, devrais-je:

if len(my_list) >= 4:
    x = my_list[3]
else:
    x = 'NO_ABC'

Ou:

try:
    x = my_list[3]
except IndexError:
    x = 'NO_ABC'

Quelques réflexions ...
PEP 20 dit:

Les erreurs ne devraient jamais passer silencieusement.
À moins d'être explicitement réduit au silence.

L'utilisation de a tryau lieu de an doit-elle ifêtre interprétée comme une erreur passant silencieusement? Et si tel est le cas, est-ce que vous le faites taire explicitement en l'utilisant de cette manière, pour que cela fonctionne?


Je ne parle pas de situations où vous ne pouvez faire les choses que d'une seule manière; par exemple:

try:
    import foo
except ImportError:
    import baz
chown
la source

Réponses:

161

Vous devriez préférer try/exceptà if/elsesi cela se traduit par

  • accélérations (par exemple en empêchant les recherches supplémentaires)
  • code plus propre (moins de lignes / plus facile à lire)

Souvent, ceux-ci vont de pair.


accélérations

Dans le cas où vous essayez de trouver un élément dans une longue liste en:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

l'essai, sauf est la meilleure option lorsque le indexest probablement dans la liste et que IndexError n'est généralement pas déclenché. De cette façon, vous évitez d'avoir à effectuer une recherche supplémentaire par if index < len(my_list).

Python encourage l'utilisation d'exceptions, que vous gérez est une phrase de Dive Into Python . Votre exemple ne gère pas seulement l'exception (avec grâce), plutôt que de la laisser passer silencieusement , l'exception se produit également uniquement dans le cas exceptionnel où l'index n'est pas trouvé (d'où le mot exception !).


code de nettoyage

La documentation officielle Python mentionne EAFP : Il est plus facile de demander pardon que la permission et Rob Knight note que détecter les erreurs plutôt que les éviter peut entraîner un code plus propre et plus facile à lire. Son exemple le dit comme ceci:

Pire (LBYL 'regarde avant de sauter') :

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

Mieux (EAFP: plus facile de demander pardon que la permission) :

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None
Remi
la source
if index in mylistteste si l'index est un élément de mylist, pas un index possible. Vous voudriez if index < len(mylist)plutôt.
ychaouche
1
Cela a du sens, mais où puis-je trouver la documentation qui précise quelles exceptions possibles peuvent être levées pour le int (). docs.python.org/3/library/functions.html#int Je ne parviens pas à trouver cette information ici.
BrutalSimplicity
En revanche, vous pouvez ajouter ce cas (de off. Doc ) à votre réponse, quand vous devriez préférer if/elsequetry/catch
rappongy
20

Dans ce cas particulier, vous devez utiliser tout autre chose:

x = myDict.get("ABC", "NO_ABC")

En général, cependant: si vous pensez que le test échoue fréquemment, utilisez if. Si le test est coûteux par rapport au simple essai de l'opération et à la détection de l'exception en cas d'échec, utilisez try. Si aucune de ces conditions ne s'applique, optez pour ce qui se lit plus facilement.

duskwuff -inactif-
la source
1
+1 pour l'explication sous l'exemple de code, qui est parfait.
ktdrv
4
Je pense qu'il est assez clair que ce n'est pas ce qu'il demandait, et il a maintenant édité le message pour le rendre encore plus clair.
agf
9

Utiliser tryet exceptdirectement plutôt qu'à l'intérieur d'un ifgarde doit toujours être fait s'il y a une possibilité de condition de concurrence. Par exemple, si vous voulez vous assurer qu'un répertoire existe, ne faites pas ceci:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

Si un autre thread ou processus crée le répertoire entre isdiret mkdir, vous quitterez. À la place, faites ceci:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

Cela ne quittera que si le répertoire «foo» ne peut pas être créé.

John Cowan
la source
7

S'il est trivial de vérifier si quelque chose échouera avant de le faire, vous devriez probablement favoriser cela. Après tout, la construction d'exceptions (y compris leurs retraits associés) prend du temps.

Des exceptions doivent être utilisées pour:

  1. des choses inattendues, ou ...
  2. les choses où vous devez sauter plus d'un niveau de logique (par exemple, où a breakne vous mène pas assez loin), ou ...
  3. des choses pour lesquelles vous ne savez pas exactement ce qui va gérer l'exception à l'avance, ou ...
  4. les choses où la vérification à l'avance de l'échec est coûteuse (par rapport à la simple tentative de l'opération)

Notez que souvent, la vraie réponse est «ni l'un ni l'autre» - par exemple, dans votre premier exemple, ce que vous devriez vraiment faire est simplement d'utiliser .get()pour fournir une valeur par défaut:

x = myDict.get('ABC', 'NO_ABC')
ambre
la source
sauf que if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'c'est en fait souvent plus rapide que l'utilisation get, malheureusement. Ne pas dire que c'est le critère le plus important, mais c'est quelque chose dont il faut être conscient.
agf
@agf: mieux vaut écrire un code clair et concis. Si quelque chose doit être optimisé plus tard, il est facile de revenir et de le réécrire, mais c2.com/cgi/wiki?PrematureOptimization
Ambre
Je le sais ; mon point était que if / elseet try / exceptpeut avoir leur place même lorsqu'il existe des alternatives spécifiques à un cas parce qu'elles ont des caractéristiques de performance différentes.
agf
@agf, savez-vous si la méthode get () sera améliorée dans les futures versions pour être (au moins) aussi rapide que la recherche explicite? Au fait, lorsque vous recherchez deux fois (comme si 'ABC' dans d: d ['ABC']), est-ce que try: d ['ABC'] sauf KeyError: ... n'est pas le plus rapide?
Remi
2
@Remi La partie lente de .get()est la recherche d'attributs et la surcharge des appels de fonctions au niveau Python; l'utilisation de mots-clés sur des éléments intégrés va fondamentalement à C. Je ne pense pas que cela deviendra trop rapide de si tôt. En ce qui concerne ifvs. try, la méthode read dict.get () renvoie un pointeur contenant des informations sur les performances. Le rapport entre les résultats et les échecs est important ( trypeut être plus rapide si la clé existe presque toujours), tout comme la taille du dictionnaire.
agf
5

Comme le mentionnent les autres articles, cela dépend de la situation. Il y a quelques dangers à utiliser try / sauf au lieu de vérifier la validité de vos données à l'avance, en particulier lorsque vous les utilisez sur des projets plus importants.

  • Le code dans le bloc try peut avoir une chance de faire toutes sortes de ravages avant que l'exception ne soit interceptée - si vous vérifiez de manière proactive au préalable avec une instruction if, vous pouvez éviter cela.
  • Si le code appelé dans votre bloc try déclenche un type d'exception commun, comme TypeError ou ValueError, vous risquez de ne pas intercepter la même exception que vous vous attendiez à attraper - cela peut être quelque chose d'autre qui déclenche la même classe d'exception avant ou après même avoir atteint la ligne où votre exception peut être déclenchée.

par exemple, supposons que vous ayez:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

IndexError ne dit rien sur le fait de savoir si cela s'est produit lors de la tentative d'obtention d'un élément de index_list ou my_list.

Peter
la source
4

L'utilisation d'un essai au lieu d'un if devrait-elle être interprétée comme une erreur passant silencieusement? Et si tel est le cas, est-ce que vous le faites taire explicitement en l'utilisant de cette manière, pour que cela fonctionne?

Utiliser, tryc'est reconnaître qu'une erreur peut passer, ce qui est le contraire de la faire passer en silence. L'utilisation exceptfait que ça ne passe pas du tout.

L'utilisation try: except:est préférable dans les cas où la if: else:logique est plus compliquée. Le simple vaut mieux que le complexe; complexe vaut mieux que compliqué; et il est plus facile de demander pardon que la permission.

Ce dont "les erreurs ne devraient jamais passer en silence" est un avertissement, c'est le cas où le code pourrait déclencher une exception que vous connaissez, et où votre conception en admet la possibilité, mais vous n'avez pas conçu de manière à gérer l'exception. À mon avis, faire taire explicitement une erreur reviendrait à faire quelque chose comme passdans un exceptbloc, ce qui ne devrait être fait qu'en comprenant que "ne rien faire" est vraiment la bonne gestion des erreurs dans une situation particulière. (C'est l'une des rares fois où j'ai l'impression qu'un commentaire dans un code bien écrit est probablement vraiment nécessaire.)

Cependant, dans votre exemple particulier, ni l'un ni l'autre n'est approprié:

x = myDict.get('ABC', 'NO_ABC')

La raison pour laquelle tout le monde le souligne - même si vous reconnaissez votre désir de comprendre en général et votre incapacité à trouver un meilleur exemple - est que des mesures parallèles équivalentes existent en fait dans de nombreux cas, et les rechercher est la première étape pour résoudre le problème.

Karl Knechtel
la source
3

Chaque fois que vous utilisez try/exceptpour contrôler le flux, posez-vous les questions suivantes:

  1. Est-il facile de voir quand le trybloc réussit et quand il échoue?
  2. Êtes-vous conscient de tous les effets secondaires à l'intérieur du trybloc?
  3. Connaissez-vous tous les cas dans lesquels le trybloc lève l'exception?
  4. Si la mise en œuvre du trybloc change, votre flux de contrôle se comportera-t-il toujours comme prévu?

Si la réponse à une ou plusieurs de ces questions est «non», il peut y avoir beaucoup de pardon à demander; très probablement de votre futur moi.


Un exemple. J'ai récemment vu du code dans un projet plus vaste qui ressemblait à ceci:

try:
    y = foo(x)
except ProgrammingError:
    y = bar(x)

En parlant au programmeur, il s'est avéré que le flux de contrôle prévu était:

Si x est un entier, faites y = foo (x).

Si x est une liste d'entiers, faites y = bar (x).

Cela a fonctionné car fooune requête de base de données a été effectuée et la requête serait réussie si xétait un entier et lancer un ProgrammingErrorif xétait une liste.

L'utilisation try/exceptest un mauvais choix ici:

  1. Le nom de l'exception,, ProgrammingErrorn'indique pas le problème réel (qui xn'est pas un entier), ce qui rend difficile de voir ce qui se passe.
  2. Le ProgrammingErrorest déclenché lors d'un appel à la base de données, ce qui fait perdre du temps. Les choses deviendraient vraiment horribles s'il s'avérait que cela fooécrit quelque chose dans la base de données avant de lever une exception ou de modifier l'état d'un autre système.
  3. Il n'est pas clair si ProgrammingErrorest levé uniquement quand xest une liste d'entiers. Supposons par exemple qu'il y ait une faute de frappe dans foola requête de base de données de. Cela pourrait également augmenter un ProgrammingError. La conséquence est que bar(x)maintenant est également appelé quand xest un entier. Cela pourrait soulever des exceptions cryptiques ou produire des résultats imprévisibles.
  4. Le try/exceptbloc ajoute une exigence à toutes les futures implémentations de foo. Chaque fois que nous changeons foo, nous devons maintenant réfléchir à la façon dont il gère les listes et nous assurer qu'il génère une erreur ProgrammingErroret pas, disons, une AttributeErrorerreur ou pas du tout.
Elias Strehle
la source
0

Pour une signification générale, vous pouvez envisager de lire les expressions idiomatiques et anti-idiomatiques en Python: exceptions .

Dans votre cas particulier, comme d'autres l'ont indiqué, vous devez utiliser dict.get():

get (clé [, par défaut])

Renvoie la valeur de la clé si la clé est dans le dictionnaire, sinon la valeur par défaut. Si la valeur par défaut n'est pas indiquée, la valeur par défaut est None, de sorte que cette méthode ne déclenche jamais une KeyError.

etuardu
la source
Je ne pense pas que le lien couvre du tout cette situation. Ses exemples concernent la gestion de choses qui représentent des erreurs réelles , et non l'utilisation de la gestion des exceptions pour les situations attendues.
agf
Le premier lien est obsolète.
Timofei Bondarev