Obtenez la description de l'exception et la trace de la pile qui a provoqué une exception, le tout sous forme de chaîne


424

J'ai vu beaucoup de messages sur la trace de pile et les exceptions en Python. Mais je n'ai pas trouvé ce dont j'avais besoin.

J'ai un morceau de code Python 2.7 qui peut déclencher une exception. Je voudrais l'attraper et attribuer à une chaîne sa description complète et la trace de la pile qui a provoqué l'erreur (simplement tout ce que nous utilisons pour voir sur la console). J'ai besoin de cette chaîne pour l'imprimer dans une zone de texte dans l'interface graphique.

Quelque chose comme ça:

try:
    method_that_can_raise_an_exception(params)
except Exception as e:
    print_to_textbox(complete_exception_description(e))

Le problème est: quelle est la fonction complete_exception_description?

Réponses:


617

Voir le tracebackmodule, en particulier la format_exc()fonction. Ici .

import traceback

try:
    raise ValueError
except ValueError:
    tb = traceback.format_exc()
else:
    tb = "No error"
finally:
    print tb

2
Est-ce que cela ne fonctionne qu'avec la dernière erreur? Que se passe-t-il si vous commencez à transmettre l'erreur à d'autres bits de code? J'écris une log_error(err)fonction.
AnnanFay

Cela fonctionne avec l'erreur qui a été interceptée et gérée.
kindall

74

Créons un stacktrace décemment compliqué, afin de démontrer que nous obtenons le stacktrace complet:

def raise_error():
    raise RuntimeError('something bad happened!')

def do_something_that_might_error():
    raise_error()

Journalisation de la trace de pile complète

Une meilleure pratique consiste à configurer un enregistreur pour votre module. Il connaîtra le nom du module et pourra changer de niveau (entre autres attributs, comme les gestionnaires)

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

Et nous pouvons utiliser cet enregistreur pour obtenir l'erreur:

try:
    do_something_that_might_error()
except Exception as error:
    logger.exception(error)

Quels journaux:

ERROR:__main__:something bad happened!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Et donc nous obtenons la même sortie que lorsque nous avons une erreur:

>>> do_something_that_might_error()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

Obtenir juste la chaîne

Si vous voulez vraiment juste la chaîne, utilisez traceback.format_excplutôt la fonction, démontrant la journalisation de la chaîne ici:

import traceback
try:
    do_something_that_might_error()
except Exception as error:
    just_the_string = traceback.format_exc()
    logger.debug(just_the_string)

Quels journaux:

DEBUG:__main__:Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in do_something_that_might_error
  File "<stdin>", line 2, in raise_error
RuntimeError: something bad happened!

1
est-ce la meilleure méthode lors de l'utilisation de python 3 (comparé par exemple à certaines des réponses ci-dessous)?
Yunti

1
@Yunti Je crois que cette API a été cohérente sur Python 2 et 3.
Aaron Hall

Le formatage de cette réponse a été discuté sur meta: meta.stackoverflow.com/questions/386477/… .
Lundin

J'ai envoyé une modification à ce qui suit, mais je n'étais pas connecté, ce qui montre que je suis anonyme: except Exception as e: logger.exception("<<clearly and distinctly describe what failed here>>", exc_info=e)
arntg

@arntg J'apprécie que vous essayiez d'aider, mais cette modification serait un changement nuisible. Veuillez être beaucoup plus prudent à l'avenir pour bien comprendre les API que vous essayez d'utiliser. Dans ce cas, l' exc_infoargument attend un "tuple d'exception" alors que le errorest une instance de l' Exceptionobjet (ou sous-classe), et il n'est pas nécessaire de passer errorà e.
Aaron Hall

39

Avec Python 3, le code suivant formatera un Exceptionobjet exactement comme il serait obtenu en utilisant traceback.format_exc():

import traceback

try: 
    method_that_can_raise_an_exception(params)
except Exception as ex:
    print(''.join(traceback.format_exception(etype=type(ex), value=ex, tb=ex.__traceback__)))

L'avantage étant que seul l' Exceptionobjet est nécessaire (grâce à l' __traceback__attribut enregistré ), et peut donc être plus facilement passé en argument à une autre fonction pour un traitement ultérieur.


1
C'est mieux que sys.exc_info () qui n'est pas un bon style OO et utilise une variable globale.
WeiChing 林 煒 清

Cela demande spécifiquement comment obtenir le traceback de l'objet d'exception comme vous l'avez fait ici: stackoverflow.com/questions/11414894/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Il existe un moyen Python3 plus simple sans utiliser .__traceback__et type, voir stackoverflow.com/a/58764987/5717886
don_vanchos

34
>>> import sys
>>> import traceback
>>> try:
...   5 / 0
... except ZeroDivisionError as e:
...   type_, value_, traceback_ = sys.exc_info()
>>> traceback.format_tb(traceback_)
['  File "<stdin>", line 2, in <module>\n']
>>> value_
ZeroDivisionError('integer division or modulo by zero',)
>>> type_
<type 'exceptions.ZeroDivisionError'>
>>>
>>> 5 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

Vous utilisez sys.exc_info () pour collecter les informations et les fonctions du tracebackmodule pour les formater. Voici quelques exemples de formatage.

La chaîne d'exception entière est à:

>>> ex = traceback.format_exception(type_, value_, traceback_)
>>> ex
['Traceback (most recent call last):\n', '  File "<stdin>", line 2, in <module>\n', 'ZeroDivisionError: integer division or modulo by zero\n']

9

Pour ceux qui utilisent Python-3

En utilisant le tracebackmodule et exception.__traceback__on peut extraire la trace de pile comme suit:

  • récupérer la trace de pile actuelle en utilisanttraceback.extract_stack()
  • supprimer les trois derniers éléments (car ce sont des entrées dans la pile qui m'ont amené à ma fonction de débogage)
  • ajouter l' __traceback__objet d'exception à l'aide detraceback.extract_tb()
  • formater le tout en utilisant traceback.format_list()
import traceback
def exception_to_string(excp):
   stack = traceback.extract_stack()[:-3] + traceback.extract_tb(excp.__traceback__)  # add limit=?? 
   pretty = traceback.format_list(stack)
   return ''.join(pretty) + '\n  {} {}'.format(excp.__class__,excp)

Une démonstration simple:

def foo():
    try:
        something_invalid()
    except Exception as e:
        print(exception_to_string(e))

def bar():
    return foo()

Nous obtenons la sortie suivante lorsque nous appelons bar():

  File "./test.py", line 57, in <module>
    bar()
  File "./test.py", line 55, in bar
    return foo()
  File "./test.py", line 50, in foo
    something_invalid()

  <class 'NameError'> name 'something_invalid' is not defined

Il ressemble à un code compliqué illisible. Dans Python 3.5+, il existe un moyen plus élégant et plus simple: stackoverflow.com/a/58764987/5717886
don_vanchos

6

Vous pouvez également envisager d'utiliser le module Python intégré, cgitb , pour obtenir de très bonnes informations d'exception bien formatées, y compris les valeurs des variables locales, le contexte du code source, les paramètres de fonction, etc.

Par exemple pour ce code ...

import cgitb
cgitb.enable(format='text')

def func2(a, divisor):
    return a / divisor

def func1(a, b):
    c = b - 5
    return func2(a, c)

func1(1, 5)

nous obtenons cette sortie d'exception ...

ZeroDivisionError
Python 3.4.2: C:\tools\python\python.exe
Tue Sep 22 15:29:33 2015

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 c:\TEMP\cgittest2.py in <module>()
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
func1 = <function func1>

 c:\TEMP\cgittest2.py in func1(a=1, b=5)
    7 def func1(a, b):
    8   c = b - 5
    9   return func2(a, c)
   10
   11 func1(1, 5)
global func2 = <function func2>
a = 1
c = 0

 c:\TEMP\cgittest2.py in func2(a=1, divisor=0)
    3
    4 def func2(a, divisor):
    5   return a / divisor
    6
    7 def func1(a, b):
a = 1
divisor = 0
ZeroDivisionError: division by zero
    __cause__ = None
    __class__ = <class 'ZeroDivisionError'>
    __context__ = None
    __delattr__ = <method-wrapper '__delattr__' of ZeroDivisionError object>
    __dict__ = {}
    __dir__ = <built-in method __dir__ of ZeroDivisionError object>
    __doc__ = 'Second argument to a division or modulo operation was zero.'
    __eq__ = <method-wrapper '__eq__' of ZeroDivisionError object>
    __format__ = <built-in method __format__ of ZeroDivisionError object>
    __ge__ = <method-wrapper '__ge__' of ZeroDivisionError object>
    __getattribute__ = <method-wrapper '__getattribute__' of ZeroDivisionError object>
    __gt__ = <method-wrapper '__gt__' of ZeroDivisionError object>
    __hash__ = <method-wrapper '__hash__' of ZeroDivisionError object>
    __init__ = <method-wrapper '__init__' of ZeroDivisionError object>
    __le__ = <method-wrapper '__le__' of ZeroDivisionError object>
    __lt__ = <method-wrapper '__lt__' of ZeroDivisionError object>
    __ne__ = <method-wrapper '__ne__' of ZeroDivisionError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of ZeroDivisionError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of ZeroDivisionError object>
    __repr__ = <method-wrapper '__repr__' of ZeroDivisionError object>
    __setattr__ = <method-wrapper '__setattr__' of ZeroDivisionError object>
    __setstate__ = <built-in method __setstate__ of ZeroDivisionError object>
    __sizeof__ = <built-in method __sizeof__ of ZeroDivisionError object>
    __str__ = <method-wrapper '__str__' of ZeroDivisionError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __suppress_context__ = False
    __traceback__ = <traceback object>
    args = ('division by zero',)
    with_traceback = <built-in method with_traceback of ZeroDivisionError object>

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "cgittest2.py", line 11, in <module>
    func1(1, 5)
  File "cgittest2.py", line 9, in func1
    return func2(a, c)
  File "cgittest2.py", line 5, in func2
    return a / divisor
ZeroDivisionError: division by zero

5

Si vous souhaitez obtenir les mêmes informations lorsqu'une exception n'est pas gérée, vous pouvez faire quelque chose comme ça. Faites import tracebacket ensuite:

try:
    ...
except Exception as e:
    print(traceback.print_tb(e.__traceback__))

J'utilise Python 3.7.


3

Pour Python 3.5+ :

Ainsi, vous pouvez obtenir la trace de pile de votre exception comme de toute autre exception. Utilisez traceback.TracebackException-le (remplacez simplement expar votre exception):

print("".join(traceback.TracebackException.from_exception(ex).format())

Un exemple étendu et d'autres fonctionnalités pour ce faire:

import traceback

try:
    1/0
except Exception as ex:
    print("".join(traceback.TracebackException.from_exception(ex).format()) == traceback.format_exc() == "".join(traceback.format_exception(type(ex), ex, ex.__traceback__))) # This is True !!
    print("".join(traceback.TracebackException.from_exception(ex).format()))

La sortie sera quelque chose comme ceci:

True
Traceback (most recent call last):
  File "untidsfsdfsdftled.py", line 29, in <module>
    1/0
ZeroDivisionError: division by zero

1

mes 2 cents:

import sys, traceback
try: 
  ...
except Exception, e:
  T, V, TB = sys.exc_info()
  print ''.join(traceback.format_exception(T,V,TB))

1

Si votre objectif est de faire en sorte que le message d'exception et de stacktrace ressemble exactement au cas où python lève une erreur, ce qui suit fonctionne à la fois dans python 2 + 3:

import sys, traceback


def format_stacktrace():
    parts = ["Traceback (most recent call last):\n"]
    parts.extend(traceback.format_stack(limit=25)[:-2])
    parts.extend(traceback.format_exception(*sys.exc_info())[1:])
    return "".join(parts)

# EXAMPLE BELOW...

def a():
    b()


def b():
    c()


def c():
    d()


def d():
    assert False, "Noooh don't do it."


print("THIS IS THE FORMATTED STRING")
print("============================\n")

try:
    a()
except:
    stacktrace = format_stacktrace()
    print(stacktrace)

print("THIS IS HOW PYTHON DOES IT")
print("==========================\n")
a()

Cela fonctionne en supprimant le dernier format_stacktrace()appel de la pile et en rejoignant le reste. Lorsqu'il est exécuté, l'exemple ci-dessus donne la sortie suivante:

THIS IS THE FORMATTED STRING
============================

Traceback (most recent call last):
  File "test.py", line 31, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.

THIS IS HOW PYTHON DOES IT
==========================

Traceback (most recent call last):
  File "test.py", line 38, in <module>
    a()
  File "test.py", line 12, in a
    b()
  File "test.py", line 16, in b
    c()
  File "test.py", line 20, in c
    d()
  File "test.py", line 24, in d
    assert False, "Noooh don't do it."
AssertionError: Noooh don't do it.

0

J'ai défini la classe d'assistance suivante:

import traceback
class TracedExeptions(object):
    def __init__(self):
        pass
    def __enter__(self):
        pass

    def __exit__(self, etype, value, tb):
      if value :
        if not hasattr(value, 'traceString'):
          value.traceString = "\n".join(traceback.format_exception(etype, value, tb))
        return False
      return True

Que je pourrai utiliser plus tard comme ceci:

with TracedExeptions():
  #some-code-which-might-throw-any-exception

Et plus tard peut le consommer comme ceci:

def log_err(ex):
  if hasattr(ex, 'traceString'):
    print("ERROR:{}".format(ex.traceString));
  else:
    print("ERROR:{}".format(ex));

(Contexte: j'ai été truqué à cause de l'utilisation de Promises avec Exceptions, qui transmet malheureusement les exceptions levées à un endroit à un gestionnaire on_rejected à un autre endroit, et il est donc difficile d'obtenir le retraçage de l'emplacement d'origine)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.