Utilisation de Python «relance»

197

Quelle est la différence entre raiseet raise fromen Python?

try:
    raise ValueError
except Exception as e:
    raise IndexError

qui donne

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError
IndexError

et

try:
    raise ValueError
except Exception as e:
    raise IndexError from e

qui donne

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError from e
IndexError
félin noir
la source
9
Avez-vous lu PEP-3134 ?
jonrsharpe
4
Maintenant, utilisez raise IndexError from None, disons.
Martijn Pieters
11
Il h. raise IndexError from Falsesoulève un TypeError, pas un IndexError. Fait ma journée.
Mad Physicist le
Je ne sais pas si c'est le bon endroit pour le mentionner, mais pour quiconque utilise Spyder: toute cette construction ne fonctionne pas là-bas. C'est un problème depuis plus de 3 ans maintenant ( github.com/spyder-ide/spyder/issues/2943 ), mais ils semblent penser qu'il n'y a pas besoin d'exceptions chaînées.
Emil Bode

Réponses:

230

La différence est que lorsque vous utilisez from, l' __cause__attribut est défini et le message indique que l'exception a été directement causée par . Si vous omettez, fromalors no __cause__est défini, mais l' __context__attribut peut également être défini et le suivi affiche alors le contexte comme lors de la gestion de quelque chose d'autre s'est produit .

La définition de __context__se produit si vous avez utilisé raisedans un gestionnaire d'exceptions; si vous avez utilisé raisen'importe où ailleurs, no __context__est défini non plus.

Si a __cause__est défini, un __suppress_context__ = Trueindicateur est également défini sur l'exception; lorsque __suppress_context__est défini sur True, le __context__est ignoré lors de l'impression d'une trace.

Lorsque vous déclenchez à partir d'un gestionnaire d'exceptions où vous ne voulez pas afficher le contexte (ne voulez pas qu'un message lors de la gestion d'une autre exception s'est produit ), utilisez raise ... from Nonepour définir __suppress_context__sur True.

En d'autres termes, Python définit un contexte sur les exceptions afin que vous puissiez introspecter où une exception a été déclenchée, vous permettant de voir si une autre exception a été remplacée par elle. Vous pouvez également ajouter une cause à une exception, en rendant le traçage explicite sur l'autre exception (utilisez un libellé différent), et le contexte est ignoré (mais peut toujours être introspectionné lors du débogage). L'utilisation raise ... from Nonevous permet de supprimer le contexte en cours d'impression.

Voir la documentation de la raisedéclaration :

La fromclause est utilisée pour le chaînage des exceptions: si elle est donnée, la deuxième expression doit être une autre classe ou instance d'exception, qui sera ensuite attachée à l'exception levée en tant __cause__qu'attribut (qui est accessible en écriture). Si l'exception levée n'est pas gérée, les deux exceptions seront imprimées:

>>> try:
...     print(1 / 0)
... except Exception as exc:
...     raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

Un mécanisme similaire fonctionne implicitement si une exception est déclenchée à l'intérieur d'un gestionnaire d'exceptions ou d'une finallyclause: l'exception précédente est alors attachée en tant __context__qu'attribut de la nouvelle exception :

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

Consultez également la documentation sur les exceptions intégrées pour plus de détails sur le contexte et les informations de cause attachées aux exceptions.

Martijn Pieters
la source
11
Y a-t-il une raison d'enchaîner explicitement les exceptions en utilisant fromet __cause__au lieu de l'implicite __context__? Y a-t-il des cas où l'on attacherait une exception différente de celle interceptée par le except?
darkfeline
13
@darkfeline: disons que votre API de base de données prend en charge l'ouverture de bases de données à partir de diverses sources, y compris le Web et le disque. Votre API lèvera toujours un DatabaseErrorsi l'ouverture de la base de données échoue. Mais si l'échec est le résultat d'un IOErroréchec de l'ouverture d'un fichier ou d'un HTTPErroréchec du fonctionnement d'une URL, c'est le contexte que vous souhaitez inclure explicitement, de sorte que le développeur utilisant l'API puisse déboguer la raison. À ce moment-là, vous utilisez raise DatabaseError from original_exception.
Martijn Pieters
4
@darkfeline: Si ce développeur encapsule l'utilisation de l'API de base de données dans sa propre API et souhaite la transmettre IOErrorou la transmettre HTTPErrorà ses consommateurs, il devra alors l'utiliser raise NewException from databaseexception.__cause__, en utilisant désormais une exception différente de DatabaseExceptioncelle qu'il vient de capturer.
Martijn Pieters
2
@ dan3: non, il n'y en a pas. Le chaînage d'exceptions est purement une fonctionnalité Python 3.
Martijn Pieters
5
@ laike9m: vous voulez dire quand vous gérez une exception fooet que vous voulez soulever une nouvelle exception bar? Ensuite, vous pouvez utiliser raise bar from fooet avoir l'état Python qui a foo directement causébar . Si vous ne l' utilisez pasfrom foo , Python imprimera toujours les deux, mais déclarera que lors de la gestion foo, a barété déclenché , un message différent, destiné à signaler un bogue possible dans la gestion des erreurs.
Martijn Pieters