web-dev-qa-db-fra.com

Extraire les informations de trace d'un objet d'exception

Étant donné un objet Exception (d'origine inconnue), existe-t-il un moyen d'obtenir son suivi? J'ai un code comme ça:

def stuff():
   try:
       .....
       return useful
   except Exception as e:
       return e

result = stuff()
if isinstance(result, Exception):
    result.traceback <-- How?

Comment puis-je extraire la trace de l'objet Exception une fois que je l'ai?

93
georg

La réponse à cette question dépend de la version de Python que vous utilisez.

Dans Python 3

C'est simple: les exceptions sont équipées d'un attribut __traceback__ Contenant le suivi. Cet attribut est également accessible en écriture et peut être facilement défini à l’aide de la méthode des exceptions with_traceback:

raise Exception("foo occurred").with_traceback(tracebackobj)

Ces fonctionnalités sont minimalement décrites dans le cadre de la documentation raise .

Tout le crédit pour cette partie de la réponse devrait aller à Vyctor, qui a d'abord posté cette information . Je l'inclue ici uniquement parce que cette réponse est bloquée en haut et que Python 3 devient plus courant.

Dans Python 2

C'est ennuyeusement complexe. Le problème avec les retraçage réside dans le fait qu’ils ont des références aux cadres de pile et que ceux-ci contiennent des références aux retraits qui renvoient aux références de pile qui ont des références à ... vous avez l’idée. Cela pose des problèmes pour le ramasse-miettes. (Merci à ecatmur pour l'avoir signalé.)

La meilleure façon de résoudre ce problème serait de procéder à une intervention chirurgicale interrompre le cycle après avoir quitté la clause except, ce que fait Python 3. La solution Python 2 est beaucoup plus laide: une fonction ad-hoc vous est fournie, sys.exc_info() , qui uniquement fonctionne à l'intérieur de la clause except. Il retourne un tuple contenant l'exception, le type d'exception et le suivi pour toute exception en cours de traitement.

Donc, si vous êtes dans la clause except, vous pouvez utiliser la sortie de sys.exc_info() avec le module traceback pour effectuer diverses tâches utiles:

>>> import sys, traceback
>>> def raise_exception():
...     try:
...         raise Exception
...     except Exception:
...         ex_type, ex, tb = sys.exc_info()
...         traceback.print_tb(tb)
...     finally:
...         del tb
... 
>>> raise_exception()
  File "<stdin>", line 3, in raise_exception

Mais comme votre édition l’indique, vous essayez d’obtenir la trace que aurait aurait été imprimée si votre exception n’avait pas été gérée, après que déjà été traité. C'est une question beaucoup plus difficile. Malheureusement, sys.exc_info Renvoie (None, None, None) Lorsqu'aucune exception n'est gérée. Les autres attributs liés sys ne vous aident pas non plus. sys.exc_traceback Est déconseillé et indéfini lorsqu'aucune exception n'est gérée; sys.last_traceback Semble parfait, mais il ne semble être défini que lors de sessions interactives.

Si vous pouvez contrôler la manière dont l'exception est déclenchée, vous pourrez peut-être utiliser inspect et une exception personnalisée pour stocker certaines informations. Mais je ne suis pas tout à fait sûr de savoir comment cela fonctionnerait.

Pour dire la vérité, attraper et renvoyer une exception est une sorte de chose inhabituelle à faire. Cela pourrait être un signe que vous devez quand même refactorer.

76
senderle

Depuis Python 3.0[PPE 3109] la classe intégrée Exception a un __traceback__ attribut qui contient un traceback object (avec Python 3.2.3):

>>> try:
...     raise Exception()
... except Exception as e:
...     tb = e.__traceback__
...
>>> tb
<traceback object at 0x00000000022A9208>

Le problème est qu'après googler __traceback__ pendant un moment, je n’ai trouvé que quelques articles, mais aucun d’entre eux ne décrit si ou pourquoi vous devriez (ne pas) utiliser __traceback__.

Cependant, la documentation de Python 3 pour raise indique que:

Un objet de trace est normalement créé automatiquement quand une exception est levée et y est attaché en tant que __traceback__ attribut, qui est en écriture.

Donc, je suppose que c'est destiné à être utilisé.

59
Vyktor

Un moyen d’obtenir traceback en tant que chaîne d’un objet exception dans Python 3:

import traceback

# `e` is an exception object that you get from somewhere
traceback_str = ''.join(traceback.format_tb(e.__traceback__))

traceback.format_tb(...) renvoie une liste de chaînes. ''.join(...) les associe. Pour plus de références, veuillez visiter: https://docs.python.org/3/library/traceback.html#traceback.format_exc

19
Hieu

Soit dit en passant, si vous voulez obtenir le traçage complet tel que vous le verriez imprimé sur votre terminal, vous voulez ceci:

>>> try:
...     print(1/0)
... except Exception as e:
...     exc = e
...
>>> exc
ZeroDivisionError('division by zero')
>>> tb_str = traceback.format_exception(etype=type(exc), value=exc, tb=exc.__traceback__)
>>> tb_str
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: division by zero\n']
>>> print("".join(tb_str))
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

Si tu utilises format_tb comme ci-dessus, les réponses suggèrent que vous obtiendrez moins d'informations:

>>> tb_str = "".join(traceback.format_tb(exc.__traceback__))
>>> print("".join(tb_str))
  File "<stdin>", line 2, in <module>
10
Daniel Porteous

Il y a une très bonne raison pour que le traçage ne soit pas stocké dans l'exception; comme traceback contient des références aux sections locales de sa pile, il en résulterait une référence circulaire et une fuite de mémoire (temporaire) jusqu'à ce que le CPG circulaire se déclenche. (C’est pourquoi vous devriez ne jamais stocker la trace dans une variable locale =.)

La seule chose à laquelle je peux penser serait que vous monkeypatchiez les globales de stuff de sorte que, lorsqu'il pense attraper Exception, il attrape en fait un type spécialisé et l'exception se propage en tant qu'appelant:

module_containing_stuff.Exception = type("BogusException", (Exception,), {})
try:
    stuff()
except Exception:
    import sys
    print sys.exc_info()
8
ecatmur