Estrae le informazioni sul traceback da un oggetto eccezione


111

Dato un oggetto Eccezione (di origine sconosciuta) c'è un modo per ottenere il suo traceback? Ho un codice come questo:

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

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

Come posso estrarre il traceback dall'oggetto Exception una volta che l'ho ottenuto?

Risposte:


92

La risposta a questa domanda dipende dalla versione di Python che stai utilizzando.

In Python 3

È semplice: le eccezioni sono dotate di un __traceback__attributo che contiene il traceback. Anche questo attributo è scrivibile e può essere comodamente impostato utilizzando il with_tracebackmetodo delle eccezioni:

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

Queste funzionalità sono descritte in minima parte come parte della raisedocumentazione.

Tutto il merito per questa parte della risposta dovrebbe andare a Vyctor, che per primo ha pubblicato queste informazioni . Lo includo qui solo perché questa risposta è bloccata in alto e Python 3 sta diventando più comune.

In Python 2

È fastidiosamente complesso. Il problema con i traceback è che hanno riferimenti a stack frame, e gli stack frame hanno riferimenti ai traceback che hanno riferimenti a stack frame che hanno riferimenti a ... hai capito. Ciò causa problemi per il Garbage Collector. (Grazie a ecatmur per per primo.)

Il modo carino per risolvere questo problema sarebbe interrompere chirurgicamente il ciclo dopo aver lasciato la exceptclausola, che è ciò che fa Python 3. La soluzione di Python 2 è molto più brutta: ti viene fornita una funzione ad-hoc sys.exc_info(), che funziona solo all'interno della except clausola . Restituisce una tupla contenente l'eccezione, il tipo di eccezione e il traceback per qualsiasi eccezione sia attualmente gestita.

Quindi, se sei all'interno della exceptclausola, puoi utilizzare l'output di sys.exc_info()insieme al tracebackmodulo per fare varie cose utili:

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

Ma come indica la tua modifica, stai cercando di ottenere il traceback che sarebbe stato stampato se la tua eccezione non fosse stata gestita, dopo che è già stata gestita. Questa è una domanda molto più difficile. Sfortunatamente, sys.exc_inforestituisce (None, None, None)quando non viene gestita alcuna eccezione. Anche altri sysattributi correlati non aiutano. sys.exc_tracebackè deprecato e indefinito quando non viene gestita alcuna eccezione; sys.last_tracebacksembra perfetto, ma sembra essere definito solo durante le sessioni interattive.

Se è possibile controllare come l'eccezione è sollevata, si potrebbe essere in grado di utilizzare inspecte di un'eccezione personalizzata per memorizzare alcune delle informazioni. Ma non sono del tutto sicuro di come funzionerebbe.

A dire il vero, catturare e restituire un'eccezione è una cosa insolita da fare. Questo potrebbe essere un segno che devi comunque eseguire il refactoring.


Sono d'accordo sul fatto che la restituzione delle eccezioni sia in qualche modo non convenzionale, ma guarda la mia altra domanda per qualche logica alla base di questo.
georg

@ thg435, ok, allora ha più senso. Considera la mia soluzione di cui sopra usando sys.exc_infoin combinazione con l' approccio di richiamata che suggerisco sull'altra tua domanda.
senderle


69

A partire da Python 3.0 [PEP 3109] la classe incorporata Exceptionha un __traceback__attributo che contiene un traceback object(con Python 3.2.3):

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

Il problema è che dopo aver cercato__traceback__ su Google per un po 'ho trovato solo pochi articoli ma nessuno di loro descrive se o perché dovresti (non) usare __traceback__.

Tuttavia, la documentazione di Python 3 perraise dice che:

Un oggetto traceback viene normalmente creato automaticamente quando viene sollevata un'eccezione e collegata ad esso come __traceback__attributo, che è scrivibile.

Quindi presumo che sia pensato per essere usato.


4
Sì, è pensato per essere utilizzato. Da Novità di Python 3.0 "PEP 3134: Gli oggetti eccezione ora memorizzano il loro traceback come attributo traceback . Ciò significa che un oggetto eccezione ora contiene tutte le informazioni relative a un'eccezione e ci sono meno ragioni per usare sys.exc_info () ( sebbene quest'ultimo non venga rimosso). "
Maciej Szpakowski

Non capisco davvero perché questa risposta sia così titubante ed equivoca. È una proprietà documentata; perché dovrebbe non essere "pensato per essere usato"?
Mark Amery

2
@MarkAmery Forse il __nome indica che si tratta di un dettaglio di implementazione, non di una proprietà pubblica?
Basic

4
@Basic non è quello che indica qui. Convenzionalmente in Python __fooè un metodo privato ma __foo__(con anche trattini bassi finali) è un metodo "magico" (e non privato).
Mark Amery

1
Cordiali saluti, l' __traceback__attributo è sicuro al 100% da usare come preferisci, senza implicazioni GC. È difficile dirlo dalla documentazione, ma ecatmur ha trovato prove concrete .
senderle

38

Un modo per ottenere il traceback come stringa da un oggetto eccezione in 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(...)restituisce un elenco di stringhe. ''.join(...)li unisce insieme. Per ulteriori riferimenti, visitare: https://docs.python.org/3/library/traceback.html#traceback.format_tb


21

Per inciso, se vuoi effettivamente ottenere il traceback completo come lo vedresti stampato sul tuo terminale, vuoi questo:

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

Se usi format_tbcome sopra le risposte suggeriscono che otterrai meno informazioni:

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

4
Finalmente! Questa dovrebbe essere la risposta migliore. Grazie Daniel!
Dany

3
Argh, avevo passato gli ultimi 20 minuti cercando di capirlo prima di trovare questo :-) etype=type(exc)può essere omesso ora btw: "Modificato nella versione 3.5: l'argomento etype viene ignorato e dedotto dal tipo di valore." docs.python.org/3.7/library/… Testato in Python 3.7.3.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

8

C'è un'ottima ragione per cui il traceback non è memorizzato nell'eccezione; poiché il traceback contiene riferimenti alle variabili locali del suo stack, ciò comporterebbe un riferimento circolare e una perdita di memoria (temporanea) fino a quando non interviene il GC circolare (questo è il motivo per cui non dovresti mai memorizzare il traceback in una variabile locale ).

L'unica cosa a cui posso pensare sarebbe che tu scimmiottasse stuffle variabili globali in modo che quando pensa di catturare Exception, in realtà cattura un tipo specializzato e l'eccezione si propaga a te come chiamante:

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

7
Questo è sbagliato. Python 3 mette l'oggetto traceback nell'eccezione, come e.__traceback__.
Glenn Maynard

6
@GlennMaynard Python 3 risolve il problema eliminando l'obiettivo dell'eccezione all'uscita dal exceptblocco, secondo PEP 3110 .
ecatmur
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.