Obtenga la descripción de la excepción y el seguimiento de la pila que causó una excepción, todo como una cadena

423

He visto muchas publicaciones sobre seguimiento de pila y excepciones en Python. Pero no he encontrado lo que necesito.

Tengo un fragmento de código Python 2.7 que puede generar una excepción. Me gustaría atraparlo y asignar a una cadena su descripción completa y el seguimiento de la pila que causó el error (simplemente todo lo que usamos para ver en la consola). Necesito esta cadena para imprimirla en un cuadro de texto en la GUI.

Algo como esto:

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

El problema es: ¿cuál es la función complete_exception_description?

azulado
fuente

Respuestas:

616

Vea el tracebackmódulo, específicamente la format_exc()función. Aquí .

import traceback

try:
    raise ValueError
except ValueError:
    tb = traceback.format_exc()
else:
    tb = "No error"
finally:
    print tb
un poco
fuente
2
¿Esto solo funciona con el último error? ¿Qué sucede si comienzas a pasar el error a otros bits de código? Estoy escribiendo una log_error(err)función.
AnnanFay
Funciona con el error que fue capturado y manejado.
poco
74

Creemos un seguimiento de pila bastante complicado, para demostrar que obtenemos el seguimiento de pila completo:

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

def do_something_that_might_error():
    raise_error()

Registrar el seguimiento completo de la pila

Una práctica recomendada es tener un registrador configurado para su módulo. Conocerá el nombre del módulo y podrá cambiar los niveles (entre otros atributos, como los controladores)

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

Y podemos usar este registrador para obtener el error:

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

Qué registros:

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!

Y así obtenemos el mismo resultado que cuando tenemos un error:

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

Obteniendo solo la cuerda

Si realmente solo desea la cadena, use la traceback.format_excfunción en su lugar, demostrando registrar la cadena aquí:

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

Qué registros:

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!
Aaron Hall
fuente
1
¿Es este el mejor método cuando se usa Python 3 (en comparación con, por ejemplo, algunas de las respuestas a continuación)?
Yunti
1
@Yunti Creo que esta API ha sido consistente en Python 2 y 3.
Aaron Hall
El formato de esta respuesta se discutió en meta: meta.stackoverflow.com/questions/386477/… .
Lundin
Envié una edición a lo siguiente, pero no estaba conectado, por lo que se muestra como anónimo: except Exception as e: logger.exception("<<clearly and distinctly describe what failed here>>", exc_info=e)
arntg
@arntg Aprecio que estés tratando de ayudar, pero esa edición sería un cambio dañino. Tenga mucho más cuidado en el futuro para comprender completamente las API que está intentando utilizar. En este caso, el exc_infoargumento espera una "tupla de excepción" mientras que errores una instancia del Exceptionobjeto (o subclase), y no hay necesidad de cambiar errora e.
Aaron Hall
39

Con Python 3, el siguiente código formateará un Exceptionobjeto exactamente como se obtendría 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__)))

La ventaja es que solo Exceptionse necesita el objeto (gracias al __traceback__atributo registrado ) y, por lo tanto, se puede pasar más fácilmente como argumento a otra función para su posterior procesamiento.

Erwin Mayer
fuente
1
Es mejor que sys.exc_info (), que no es un buen estilo OO y usa una variable global.
WeiChing 林 煒 清
Esto pregunta específicamente cómo obtener el rastreo del objeto de excepción como lo ha hecho aquí: stackoverflow.com/questions/11414894/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Hay una manera más simple de Python3 sin usar .__traceback__y type, consulte 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

Utiliza sys.exc_info () para recopilar la información y las funciones en el tracebackmódulo para formatearla. Aquí hay algunos ejemplos para formatearlo.

Toda la cadena de excepción está en:

>>> 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']
aeter
fuente
9

Para aquellos que usan Python-3

Usando el tracebackmódulo y exception.__traceback__uno puede extraer el seguimiento de la pila de la siguiente manera:

  • agarrar el seguimiento de pila actual usandotraceback.extract_stack()
  • eliminar los últimos tres elementos (ya que esas son entradas en la pila que me llevaron a mi función de depuración)
  • anexar __traceback__desde el objeto de excepción usandotraceback.extract_tb()
  • formatear todo 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 simple demostración:

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

def bar():
    return foo()

Obtenemos el siguiente resultado cuando llamamos 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
Mike N
fuente
Parece un código complicado ilegible. En Python 3.5+ hay una manera más elegante y simple: stackoverflow.com/a/58764987/5717886
don_vanchos
6

También puede considerar usar el módulo Python incorporado , cgitb , para obtener información de excepciones realmente buena y bien formateada, incluidos valores de variables locales, contexto de código fuente, parámetros de función, etc.

Por ejemplo para este código ...

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)

obtenemos esta salida de excepción ...

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
samaspin
fuente
5

Si desea obtener la misma información dada cuando no se maneja una excepción, puede hacer algo como esto. Hacer import tracebacky luego:

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

Estoy usando Python 3.7.

SamuelN
fuente
3

Para Python 3.5+ :

Por lo tanto, puede obtener el stacktrace de su excepción como de cualquier otra excepción. Úselo traceback.TracebackException(solo reemplace excon su excepción):

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

Un ejemplo extendido y otras características para hacer esto:

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 salida será algo como esto:

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

mis 2 centavos:

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

Si su objetivo es hacer que el mensaje de excepción y stacktrace se vea exactamente como cuando Python arroja un error, lo siguiente funciona tanto en 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()

Funciona al eliminar la última format_stacktrace()llamada de la pila y unir el resto. Cuando se ejecuta, el ejemplo anterior da el siguiente resultado:

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.
Runa Kaagaard
fuente
0

Definí la siguiente clase auxiliar:

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 luego puedo usar así:

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

Y luego puede consumirlo así:

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

(Antecedentes: estaba frustrado por usar Promises junto con Exceptions, que desafortunadamente pasa las excepciones planteadas en un lugar a un controlador on_rejected en otro lugar, y por lo tanto es difícil obtener el rastreo desde la ubicación original)

qbolec
fuente