web-dev-qa-db-fra.com

Déclencher des exceptions lorsqu'une exception est déjà présente dans Python 3

Qu'arrive-t-il à ma première exception (A) lorsque la seconde (B) est déclenchée dans le code suivant?

class A(Exception): pass
class B(Exception): pass

try:
    try:
        raise A('first')
    finally:
        raise B('second')
except X as c:
    print(c)

Si exécuté avec X = A Je reçois:

Traceback (dernier appel le plus récent): 
 Fichier "raise_more_exceptions.py", ligne 6, dans 
 Augmenter A ('premier') 
 __ main __. A: premier 
 
 Lors du traitement de l'exception ci-dessus, une autre exception s'est produite: 
 
 Traceback (dernier appel en date): 
 Fichier "raise_more_exceptions.py", ligne 8, dans 
 augmenter B ('deuxième') 
 __ principal __. B: deuxième

Mais si X = B Je reçois:

seconde

Des questions

  1. Où est passée ma première exception?
  2. Pourquoi seule l'exception la plus externe peut-elle être capturée?
  3. Comment retirer l'exception la plus externe et relancer les exceptions précédentes?

Update0

Cette question concerne spécifiquement Python 3, car sa gestion des exceptions est assez différente de Python 2.

37
Matt Joiner

En réponse à la question 3, vous pouvez utiliser:

raise B('second') from None

Ce qui supprimera l'exception A traceback.

Traceback (most recent call last):
  File "raising_more_exceptions.py", line 8, in 
    raise B('second')
__main__.B: second
16
Flippym

L'exception "causante" est disponible sous la forme c .__ context__ dans votre dernier gestionnaire d'exceptions. Python utilise ces informations pour rendre un suivi plus utile. Sous Python 2.x, l'exception d'origine aurait été perdue, c'est pour Python 3 uniquement.

En règle générale, vous utiliseriez cela pour lever une exception cohérente tout en conservant l'exception d'origine accessible (bien que ce soit assez cool que cela se produise automatiquement à partir d'un gestionnaire d'exceptions, je ne le savais pas!):

try:
    do_something_involving_http()
except (URLError, socket.timeout) as ex:
    raise MyError('Network error') from ex

Plus d'informations (et d'autres choses assez utiles que vous pouvez faire) ici: http://docs.python.org/3.3/library/exceptions.html

9
Daniel

La gestion des exceptions Pythons ne traitera qu'une seule exception à la fois. Cependant, les objets d'exception sont soumis aux mêmes règles de variables et au même garbage collection que tout le reste. Par conséquent, si vous enregistrez l'objet exception dans une variable quelque part, vous pouvez le traiter plus tard, même si une autre exception est déclenchée.

Dans votre cas, lorsqu'une exception est levée lors de l'instruction "finally", Python 3 affichera le traceback de la première exception avant celle de la deuxième exception, pour être plus utile.

Un cas plus courant est que vous souhaitez déclencher une exception lors d'un traitement d'exception explicite. Ensuite, vous pouvez "enregistrer" l'exception dans l'exception suivante. Passez-le simplement comme paramètre:

>>> class A(Exception):
...     pass
... 
>>> class B(Exception):
...     pass
... 
>>> try:
...     try:
...         raise A('first')
...     except A as e:
...         raise B('second', e)
... except Exception as c:
...     print(c.args[1])
... 
first

Comme vous le voyez, vous pouvez désormais accéder à l'exception d'origine.

8
Lennart Regebro

Je pense que tous les ingrédients pour répondre à vos questions figurent déjà dans les réponses existantes. Permettez-moi de combiner et d'élaborer.

Permettez-moi de répéter le code de votre question pour fournir des références de numéro de ligne:

 1  class A(Exception): pass
 2  class B(Exception): pass
 3 
 4  try:
 5      try:
 6          raise A('first')
 7      finally:
 8          raise B('second')
 9  except X as c:
10      print(c)

Alors pour répondre à vos questions:

  1. Où est passée ma première exception?

Votre première exception A est levée à la ligne 6. La clause finally à la ligne 7 est toujours exécutée dès que le bloc try (lignes 5-6) est laissé, qu'il soit laissé en raison d'une réussite ou d'une exception levée. Pendant l'exécution de la clause finally, la ligne 8 lève une autre exception B. Comme l'ont souligné Lennart et Ignazio, une seule exception, celle qui a été soulevée le plus récemment, peut être suivie. Ainsi, dès que B est levé, le bloc global try (lignes 4-8) est quitté et l'exception B est interceptée par le except à la ligne 9 si elle correspond (si X est B).

  1. Pourquoi seule l'exception la plus externe peut-elle être capturée?

J'espère que cela est clair maintenant à partir de mon explication de 1. Vous pouvez cependant attraper l'exception intérieure/inférieure/première. Pour fusionner dans la réponse de Lennart, légèrement modifiée, voici comment intercepter les deux:

class A(Exception): pass
class B(Exception): pass
try:
    try:
        raise A('first')
    except A as e:
        raise B('second', e)
except Exception as c:
    print(c)

La sortie est:

('second', A('first',))
  1. Comment retirer l'exception la plus externe et relancer les exceptions précédentes?

Dans l'exemple de Lennart, la solution à cette question est la ligne except A as e où l'exception interne/inférieure/première est interceptée et stockée dans la variable e.

Comme un sentiment général de savoir quand attraper des exceptions, quand les ignorer et quand sur-relancer, peut-être cette question et la réponse d'Alex Martelli aide.

7
cfi
  1. Il a été jeté.
  2. Une seule exception peut être "active" à la fois par thread.
  3. Vous ne pouvez pas, sauf si vous encapsulez l'exception précédente dans la dernière exception d'une manière ou d'une autre.