Devrais-je tester if
quelque chose est valide ou juste try
pour le faire et attraper l'exception?
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 idées...
PEP 20 dit:
Les erreurs ne doivent jamais passer silencieusement.
Sauf explicitement réduit au silence.
L'utilisation de try
au lieu de if
doit-elle être interprétée comme une erreur de passage en silence? Et si tel est le cas, le supprimez-vous explicitement en l'utilisant de cette manière, pour qu'il soit donc correct?
Je non fait référence à des situations où vous ne pouvez faire les choses que dans un sens; par exemple:
try:
import foo
except ImportError:
import baz
Vous devriez préférer try/except
plutôt que if/else
si cela entraîne
Souvent, ceux-ci vont de pair.
accélérations
Dans le cas d'essayer de trouver un élément dans une longue liste par:
try:
x = my_list[index]
except IndexError:
x = 'NO_ABC'
try, except est la meilleure option lorsque index
est probablement dans la liste et que IndexError n'est généralement pas déclenché. De cette façon, vous évitez le besoin d'une recherche supplémentaire par if index < len(mylist)
.
Python encourage l'utilisation des exceptions, que vous gérez est une phrase de Dive Into Python . Votre exemple traite non seulement l'exception (gracieusement), plutôt que de le laisser passer silencieusement, mais l'exception ne survient que dans le cas exceptionnel d'index non trouvé (d'où le mot exception! ).
code de nettoyage
La documentation officielle de Python mentionne EAFP : Il est plus facile de demander pardon que d’autoriser et Rob Knight note que attraper les erreurs plutôt que de les éviterpeut avoir pour résultat 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(str)
Mieux (EAFP: Il est plus facile de demander pardon que d’autoriser):
try:
return int(str)
except (TypeError, ValueError, OverflowError): #int conversion failed
return None
Dans ce cas particulier, vous devriez utiliser autre chose:
x = myDict.get("ABC", "NO_ABC")
En général cependant: Si vous prévoyez d’échouer fréquemment le test, utilisez if
. Si le test est coûteux par rapport à la simple tentative d'opération et à la capture de l'exception si elle échoue, utilisez try
. Si aucune de ces conditions ne s'applique, optez pour une lecture plus facile.
Utiliser try
et except
directement plutôt que dans un garde if
devrait toujours être fait s'il existe 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 processus ou processus crée le répertoire entre isdir
et mkdir
, vous quitterez. Au lieu de cela, 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 se fermera que si le répertoire 'foo' ne peut pas être créé.
S'il est trivial de vérifier si quelque chose va échouer avant de le faire, vous devriez probablement le favoriser. Après tout, la construction des exceptions (y compris les retraits associés) prend du temps.
Les exceptions doivent être utilisées pour:
break
ne vous mène pas assez loin), ou ...Notez que souvent, la vraie réponse est "ni", par exemple, dans votre premier exemple, ce que vous devriez devriez faire est simplement d'utiliser .get()
pour fournir une valeur par défaut:
x = myDict.get('ABC', 'NO_ABC')
L'utilisation d'un try au lieu d'un si doit-elle être interprétée comme une erreur de passage en silence? Et si tel est le cas, le supprimez-vous explicitement en l'utilisant de cette manière, pour qu'il soit donc correct?
Utiliser try
, c'est reconnaître qu'une erreur peut passer, ce qui est le contraire de le laisser passer silencieusement. Utiliser except
fait en sorte qu’il ne passe pas du tout.
L'utilisation de try: except:
est préférable dans les cas où la logique if: else:
est plus compliquée. Simple vaut mieux que complexe; complexe est mieux que compliqué; et il est plus facile de demander pardon que de permission.
Ce que "erreurs ne devraient jamais passer silencieusement" est un avertissement, c'est le cas où le code peut générer une exception que vous connaissez et que votre conception admet la possibilité, mais que vous n'avez pas conçu de manière à traiter cette exception. À mon avis, faire taire explicitement une erreur reviendrait à faire quelque chose comme pass
dans un bloc except
, ce qui ne devrait être fait qu'avec la compréhension du fait que "ne rien faire" est vraiment le traitement correct des erreurs dans une situation donnée. (C’est l’une des rares fois où j’ai le sentiment qu’un commentaire dans un code bien écrit est probablement vraiment nécessaire.)
Toutefois, dans votre exemple particulier, aucun d’entre eux n’est approprié:
x = myDict.get('ABC', 'NO_ABC')
La raison pour laquelle tout le monde le fait remarquer - même si vous reconnaissez votre désir de comprendre en général et votre incapacité à trouver un meilleur exemple - réside dans le fait que des démarches équivalentes existent dans de nombreux cas, et que vous les recherchez, c’est la première étape pour résoudre le problème.
Comme le mentionnent les autres publications, cela dépend de la situation. L'utilisation de try/except présente quelques dangers, au lieu de vérifier la validité de vos données à l'avance, en particulier lorsque vous l'utilisez sur des projets plus importants.
par exemple, supposons que vous ayez:
try:
x = my_list[index_list[3]]
except IndexError:
x = 'NO_ABC'
IndexError n'indique pas si cela s'est produit lors de la tentative d'obtention d'un élément d'index_list ou de ma_list.
Chaque fois que vous utilisez try/except
pour le contrôle du flux, demandez-vous:
try
réussit et quand il échoue?try
?try
lève l'exception?try
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 pourrait y avoir beaucoup de pardon à demander; probablement de votre futur moi.
Un exemple . J'ai récemment vu du code dans un projet plus grand 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 le suivant:
Si x est un entier, est-ce que y = foo (x)
et si x est une liste d’entiers, faites y = bar (x).
Cela a fonctionné parce que foo
a créé une requête dans la base de données et que la requête aboutissait si x
était un entier et renvoyait ProgrammingError
si x
était une liste.
Utiliser try/except
est un mauvais choix ici:
ProgrammingError
, n'indique pas le problème réel (que x
n'est pas un entier). Cela rend difficile de comprendre ce qui se passe.ProgrammingError
est déclenchée lors d'un appel à la base de données, ce qui entraîne une perte de temps inutile. Les choses deviendraient vraiment horribles s'il s'avérait que foo
écrit quelque chose dans la base de données avant qu'elle ne lève une exception ou modifie l'état d'un autre système.ProgrammingError
est causée par le fait que x
soit une liste. Supposons, par exemple, qu'il existe une faute de frappe dans la requête de base de données foo
. Cela pourrait aussi générer une ProgrammingError
. La conséquence est que bar(x)
est maintenant également appelé si x
est un entier. Cela pourrait soulever des exceptions cryptiques ou produire des résultats imprévisibles.try/except
ajoute une exigence à toutes les implémentations futures 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 ProgrammingError
et non, disons, une AttributeError
ou aucune erreur.Pour une signification générale, vous pouvez lire Idiomes et anti-idiomes en Python: Exceptions .
Dans votre cas particulier, comme d’autres l’ont dit, vous devez utiliser dict.get()
:
get (clé [ par défaut])
Renvoie la valeur de key si key se trouve dans le dictionnaire, sinon défaut. Si default n'est pas indiqué, sa valeur par défaut est Aucune, de sorte que cette méthode ne déclenche jamais une erreur KeyError.