Ottieni la descrizione dell'eccezione e la traccia dello stack che ha causato un'eccezione, tutto come una stringa


423

Ho visto molti post sulla traccia dello stack ed eccezioni in Python. Ma non ho trovato quello di cui ho bisogno.

Ho un pezzo di codice Python 2.7 che può sollevare un'eccezione. Vorrei prenderlo e assegnare a una stringa la sua descrizione completa e la traccia dello stack che ha causato l'errore (semplicemente tutto ciò che usiamo per vedere sulla console). Ho bisogno di questa stringa per stamparla su una casella di testo nella GUI.

Qualcosa come questo:

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

Il problema è: qual è la funzione complete_exception_description?

Risposte:


616

Vedi il tracebackmodulo, in particolare la format_exc()funzione. QUI .

import traceback

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

2
Funziona solo con l'ultimo errore? Cosa succede se inizi a passare l'errore ad altri bit di codice? Sto scrivendo una log_error(err)funzione.
AnnanFay

Funziona con l'errore che è stato catturato e gestito.
kindall

74

Creiamo uno stacktrace abbastanza complicato, al fine di dimostrare che otteniamo l'intero stacktrace:

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

def do_something_that_might_error():
    raise_error()

Registrazione dell'intero stack stack

Una buona pratica è quella di avere un logger impostato per il tuo modulo. Conoscerà il nome del modulo e sarà in grado di cambiare i livelli (tra gli altri attributi, come i gestori)

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

E possiamo usare questo logger per ottenere l'errore:

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

Quali registri:

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!

E così otteniamo lo stesso output di quando abbiamo un errore:

>>> 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!

Ottenere solo la stringa

Se vuoi davvero solo la stringa, usa traceback.format_excinvece la funzione, dimostrando la registrazione della stringa qui:

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

Quali registri:

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
è questo il metodo migliore quando si utilizza python 3 (rispetto ad esempio ad alcune delle risposte di seguito)?
Yunti

1
@Yunti Credo che questa API sia stata coerente tra Python 2 e 3.
Aaron Hall

La formattazione di questa risposta è stata discussa su meta: meta.stackoverflow.com/questions/386477/… .
Lundin,

Ho inviato una modifica a quanto segue ma non ho effettuato l'accesso in modo da mostrare come anonimo: except Exception as e: logger.exception("<<clearly and distinctly describe what failed here>>", exc_info=e)
arntg

@arntg Apprezzo che stai cercando di aiutarti, ma quella modifica sarebbe un cambiamento dannoso. Prestare molta più attenzione in futuro per comprendere appieno le API che si sta tentando di utilizzare. In questo caso, l' exc_infoargomento si aspetta una "eccezione tupla", mentre il errorè un'istanza Exceptiondell'oggetto (o sottoclasse), e non v'è alcuna necessità di modifica errordi e.
Aaron Hall

39

Con Python 3, il codice seguente formatterà un Exceptionoggetto esattamente come si otterrebbe usando 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__)))

Il vantaggio è che Exceptionè necessario solo l' oggetto (grazie __traceback__all'attributo registrato ) e può quindi essere più facilmente passato come argomento a un'altra funzione per un'ulteriore elaborazione.


1
È meglio di sys.exc_info () che non è un buon stile OO e usa una variabile globale.
WeiChing 林 煒 清

Questo chiede specificamente come ottenere il traceback dall'oggetto eccezione come avete fatto qui: stackoverflow.com/questions/11414894/...
Ciro Santilli郝海东冠状病六四事件法轮功

C'è un modo più semplice senza utilizzare python3 .__traceback__e type, vedere 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

Si utilizza sys.exc_info () per raccogliere le informazioni e le funzioni nel tracebackmodulo per formattarlo. Ecco alcuni esempi per formattarlo.

L'intera stringa dell'eccezione è in:

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

Per quelli che usano Python-3

Utilizzando il tracebackmodulo e exception.__traceback__si può estrarre lo stack-trace come segue:

  • prendi lo stack-trace corrente usandotraceback.extract_stack()
  • rimuovi gli ultimi tre elementi (poiché quelli sono voci nello stack che mi hanno portato alla mia funzione di debug)
  • aggiungere l' __traceback__oggetto dall'eccezione usandotraceback.extract_tb()
  • formatta il tutto usando 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)

Una semplice dimostrazione:

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

def bar():
    return foo()

Otteniamo il seguente output quando chiamiamo 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

Sembra un codice complicato illeggibile. In Python 3.5+ c'è un modo più elegante e semplice: stackoverflow.com/a/58764987/5717886
don_vanchos

6

Puoi anche prendere in considerazione l'uso del modulo Python integrato, cgitb , per ottenere alcune informazioni sulle eccezioni davvero valide e ben formattate, inclusi valori delle variabili locali, contesto del codice sorgente, parametri delle funzioni ecc.

Ad esempio per questo codice ...

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)

otteniamo questo output di eccezione ...

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

Se desideri ottenere le stesse informazioni fornite quando non viene gestita un'eccezione, puoi fare qualcosa del genere. Fare import tracebacke quindi:

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

Sto usando Python 3.7.


3

Per Python 3.5+ :

Quindi, puoi ottenere lo stacktrace dalla tua eccezione come da qualsiasi altra eccezione. Usalo traceback.TracebackException(basta sostituire excon la tua eccezione):

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

Un esempio esteso e altre funzionalità per fare ciò:

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()))

L'output sarà qualcosa del genere:

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

1

i miei 2 centesimi:

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

1

Se il tuo obiettivo è far apparire l'eccezione e il messaggio stacktrace esattamente come quando python genera un errore, in Python 2 + 3 funziona quanto segue:

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()

Funziona rimuovendo l'ultima format_stacktrace()chiamata dallo stack e unendo il resto. Quando eseguito, l'esempio sopra fornisce il seguente output:

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

Ho definito la seguente classe di supporto:

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

Che in seguito posso usare in questo modo:

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

E in seguito può consumarlo in questo modo:

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

(Sfondo: ero frustradato a causa dell'uso di Promises insieme a Exceptions, che purtroppo passa le eccezioni sollevate in un posto a un gestore on_rejected in un altro posto, quindi è difficile ottenere il traceback dalla posizione originale)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.