web-dev-qa-db-fra.com

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

Devrais-je tester if quelque chose est valide ou juste try pour le faire et attraper l'exception?

  • Existe-t-il une documentation solide indiquant qu'une solution est préférable?
  • Y a-t-il encore une manière Pythonic?

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
112
chown

Vous devriez préférer try/except plutôt que if/else si cela entraîne

  • 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 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
134
Remi

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.

16
duskwuff

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éé.

7
John Cowan

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:

  1. des choses qui sont inattendues, ou ...
  2. les choses pour lesquelles vous devez sauter de plusieurs niveaux de logique (par exemple, une break ne vous mène pas assez loin), ou ...
  3. les choses pour lesquelles vous ne savez pas exactement ce qui sera géré à l'avance, ou ...
  4. les choses pour lesquelles la vérification à l'avance de l'échec coûte cher (par rapport à la simple tentative d'opération)

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')
7
Amber

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.

4
Karl Knechtel

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.

  • Le code dans le bloc try peut avoir une chance de causer toutes sortes de dégâts avant que l'exception ne soit capturée - si vous vérifiez de manière proactive à l'avance avec une instruction if, vous pouvez éviter cela.
  • Si le code appelé dans votre bloc try génère un type d’exception commun, tel que TypeError ou ValueError, vous risquez de ne pas intercepter la même exception que celle que vous espériez intercepter. Il peut s’agir de quelque chose d’autre qui élève la même classe d’exceptions avant ou après avoir atteint la ligne où votre exception peut être levée. 

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.

3
Peter

Chaque fois que vous utilisez try/except pour le contrôle du flux, demandez-vous:

  1. Est-il facile de voir quand le bloc try réussit et quand il échoue?
  2. Êtes-vous au courant de tous des effets secondaires à l'intérieur du bloc try?
  3. Êtes-vous au courant de tous cas dans lesquels le bloc try lève l'exception?
  4. Si la mise en œuvre du bloc 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:

  1. Le nom de l'exception, 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.
  2. La 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.
  3. Il n'est pas clair si chaque 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.
  4. Le bloc 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.
0
Elias Strehle

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.

0
etuardu