Extraer información de rastreo de un objeto de excepción

Dado un objeto de excepción (de origen desconocido), ¿hay manera de obtener su rastreo? Tengo un código como este:

def stuff(): try: ..... return useful except Exception as e: return e result = stuff() if isinstance(result, Exception): result.traceback <-- How? 

¿Cómo puedo extraer el rastreo del objeto Excepción una vez que lo tengo?

La respuesta a esta pregunta depende de la versión de Python que estés usando.

En Python 3

Es simple: las excepciones vienen equipadas con un atributo __traceback__ que contiene el rastreo. Este atributo también se puede escribir, y se puede configurar convenientemente usando el método with_traceback de excepciones:

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

Estas características se describen mínimamente como parte de la documentación del raise .

Todo el crédito por esta parte de la respuesta debe ir a Vyctor, quien primero publicó esta información . Lo incluyo aquí solo porque esta respuesta está atascada en la parte superior, y Python 3 se está volviendo más común.

En Python 2

Es molesto complejo. El problema con las trazas de retorno es que tienen referencias a marcos de stack, y los marcos de stack tienen referencias a las trazas de retorno que tienen referencias a marcos de stack que tienen referencias a … usted tiene la idea. Esto causa problemas para el recolector de basura. (Gracias a ecatmur por señalarlo primero.)

La buena forma de resolver esto sería romper el ciclo quirúrgicamente después de abandonar la cláusula de except , que es lo que hace Python 3. La solución de Python 2 es mucho más fea: se te proporciona una función ad-hoc, sys.exc_info() , que solo funciona dentro de la cláusula de except . Devuelve una tupla que contiene la excepción, el tipo de excepción y el rastreo de cualquier excepción que se esté manejando actualmente.

Entonces, si está dentro de la cláusula de except , puede usar la salida de sys.exc_info() junto con el módulo de sys.exc_info() para hacer varias cosas útiles:

 >>> 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 "", line 3, in raise_exception 

Pero como indica su edición, está intentando obtener el rastreo que se habría impreso si no se hubiera manejado su excepción, una vez que ya se haya manejado. Esa es una pregunta mucho más difícil. Desafortunadamente, sys.exc_info devuelve (None, None, None) cuando no se maneja ninguna excepción. Otros atributos de sistema relacionados tampoco ayudan. sys.exc_traceback está en desuso y es indefinido cuando no se maneja ninguna excepción; sys.last_traceback parece perfecto, pero parece que solo se define durante las sesiones interactivas.

Si puede controlar cómo se genera la excepción, es posible que pueda usar inspect y una excepción personalizada para almacenar parte de la información. Pero no estoy completamente seguro de cómo funcionaría eso.

Para decir la verdad, atrapar y devolver una excepción es algo inusual que hacer. Esto podría ser una señal de que necesita refactorizar de todos modos.

Desde Python 3.0 [PEP 3109] la Exception clase incorporada tiene un atributo __traceback__ que contiene un traceback object (con Python 3.2.3):

 >>> try: ... raise Exception() ... except Exception as e: ... tb = e.__traceback__ ... >>> tb  

El problema es que después de __traceback__ Google __traceback__ por un tiempo, encontré solo algunos artículos, pero ninguno de ellos describe si, o no, por qué deberías usar __traceback__ .

Sin embargo, la documentación de Python 3 para raise dice que:

Un objeto de rastreo normalmente se crea automáticamente cuando se __traceback__ una excepción y se adjunta a él como el atributo __traceback__ , que se puede escribir.

Así que asumo que está destinado a ser utilizado.

Una forma de obtener un rastreo como una cadena de un objeto de excepción en 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(...) devuelve una lista de cadenas. ''.join(...) une. Para obtener más información, visite: https://docs.python.org/3/library/traceback.html#traceback.format_exc

Hay una muy buena razón por la que el rastreo no está almacenado en la excepción; debido a que la traza de retorno contiene referencias a los locales de su stack, esto daría lugar a una referencia circular y una pérdida de memoria (temporal) hasta que se active el GC circular. (Es por esto que nunca debe almacenar el rastreo en una variable local ).

Casi lo único que se me ocurre es que puedas hacer una pandilla global de stuff modo que cuando piense que está detectando Exception , en realidad esté detectando un tipo especializado y la excepción se propague a ti como persona que llama:

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

Además, si realmente desea obtener el rastreo completo como lo vería impreso en su terminal, desea esto:

 >>> 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 "", line 2, in \n', 'ZeroDivisionError: division by zero\n'] >>> print("".join(tb_str)) Traceback (most recent call last): File "", line 2, in  ZeroDivisionError: division by zero 

Si usa format_tb como en las respuestas anteriores, sugiera que obtendrá menos información:

 >>> tb_str = "".join(traceback.format_tb(exc.__traceback__)) >>> print("".join(tb_str)) File "", line 2, in