"Eccezione interna" (con traceback) in Python?


146

Il mio background è in C # e di recente ho iniziato a programmare in Python. Quando viene generata un'eccezione, in genere desidero racchiuderla in un'altra eccezione che aggiunge ulteriori informazioni, pur continuando a mostrare la traccia dello stack completo. È abbastanza facile in C #, ma come posso farlo in Python?

Per esempio. in C # farei una cosa del genere:

try
{
  ProcessFile(filePath);
}
catch (Exception ex)
{
  throw new ApplicationException("Failed to process file " + filePath, ex);
}

In Python posso fare qualcosa di simile:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file ' + filePath, e)

... ma questo perde il ritorno dell'eccezione interiore!

Modifica: mi piacerebbe vedere sia i messaggi di eccezione sia le tracce dello stack e correlare i due. Cioè, voglio vedere nell'output che l'eccezione X si è verificata qui e quindi l'eccezione Y lì - lo stesso che farei in C #. È possibile in Python 2.6? Sembra che il meglio che posso fare finora (basato sulla risposta di Glenn Maynard) è:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]

Ciò include sia i messaggi sia i traceback, ma non mostra quale eccezione si è verificata in cui nel traceback.


3
La risposta accettata sta diventando obsoleta, forse dovresti considerare di accettarne un'altra.
Aaron Hall

1
@AaronHall sfortunatamente OP non è più stato visto dal 2015.
Antti Haapala

Risposte:


136

Python 2

È semplice; passare il traceback come terzo argomento da sollevare.

import sys
class MyException(Exception): pass

try:
    raise TypeError("test")
except TypeError, e:
    raise MyException(), None, sys.exc_info()[2]

Fallo sempre quando catturi un'eccezione e ne rilanci un'altra.


4
Grazie. Ciò preserva il traceback, ma perde il messaggio di errore dell'eccezione originale. Come vedo sia i messaggi che i traceback?
EMP,

6
raise MyException(str(e)), ..., ecc.
Glenn Maynard,

23
Python 3 aggiunge raise E() from tbe.with_traceback(...)
Dima Tisnek

3
@GlennMaynard è una domanda piuttosto vecchia, ma l'argomento centrale di raiseè il valore da passare all'eccezione (nel caso in cui il primo argomento sia una classe di eccezione e non un'istanza). Quindi, se si vuole eccezioni di swap, invece di fare raise MyException(str(e)), None, sys.exc_info()[2], è meglio usare questo: raise MyException, e.args, sys.exc_info()[2].
bgusach,

8
Un modo conforme a Python2 e 3 è possibile usando il pacchetto futuro: python-future.org/compatible_idioms.html#raising-exceptions Eg from future.utils import raise_e raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda,

239

Python 3

In Python 3 puoi fare quanto segue:

try:
    raise MyExceptionToBeWrapped("I have twisted my ankle")

except MyExceptionToBeWrapped as e:

    raise MyWrapperException("I'm not in a good shape") from e

Questo produrrà qualcosa del genere:

   Traceback (most recent call last):
   ...
   MyExceptionToBeWrapped: ("I have twisted my ankle")

The above exception was the direct cause of the following exception:

   Traceback (most recent call last):
   ...
   MyWrapperException: ("I'm not in a good shape")

17
raise ... from ...è davvero il modo corretto per farlo in Python 3. Ciò richiede più voti.
Nakedible,

NakediblePenso che sia perché purtroppo la maggior parte delle persone non utilizza ancora Python 3.
Tim Ludwinski,

Questo sembra accadere anche usando 'from' in Python 3
Steve Vermeulen,

Potrebbe essere eseguito il backport su Python 2. Spero che un giorno.
Marcin Wojnarski,

4
@ogrisel È possibile utilizzare il futurepacchetto per raggiungere questo obiettivo: python-future.org/compatible_idioms.html#raising-exceptions Eg from future.utils import raise_e raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda,

19

Python 3 ha la clausola raise...from per concatenare le eccezioni. La risposta di Glenn è ottima per Python 2.7, ma utilizza solo il traceback dell'eccezione originale e getta via il messaggio di errore e altri dettagli. Ecco alcuni esempi in Python 2.7 che aggiungono informazioni di contesto dall'ambito corrente nel messaggio di errore dell'eccezione originale, ma mantengono intatti altri dettagli.

Tipo di eccezione noto

try:
    sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
    self.user_id = sock_common.login(self.dbname, username, self.pwd)
except IOError:
    _, ex, traceback = sys.exc_info()
    message = "Connecting to '%s': %s." % (config['connection'],
                                           ex.strerror)
    raise IOError, (ex.errno, message), traceback

Questo tipo di raiseistruzione accetta il tipo di eccezione come prima espressione, gli argomenti del costruttore della classe di eccezione in una tupla come seconda espressione e il traceback come terza espressione. Se stai funzionando prima di Python 2.2, consulta gli avvisi sys.exc_info().

Qualsiasi tipo di eccezione

Ecco un altro esempio che ha uno scopo più generale se non sai che tipo di eccezioni il tuo codice potrebbe dover catturare. L'aspetto negativo è che perde il tipo di eccezione e genera solo un RuntimeError. Devi importare il tracebackmodulo.

except Exception:
    extype, ex, tb = sys.exc_info()
    formatted = traceback.format_exception_only(extype, ex)[-1]
    message = "Importing row %d, %s" % (rownum, formatted)
    raise RuntimeError, message, tb

Modifica il messaggio

Ecco un'altra opzione se il tipo di eccezione ti consente di aggiungere un contesto ad esso. È possibile modificare il messaggio dell'eccezione e quindi rilanciarlo.

import subprocess

try:
    final_args = ['lsx', '/home']
    s = subprocess.check_output(final_args)
except OSError as ex:
    ex.strerror += ' for command {}'.format(final_args)
    raise

Questo genera la seguente traccia dello stack:

Traceback (most recent call last):
  File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in <module>
    s = subprocess.check_output(final_args)
  File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory for command ['lsx', '/home']

Si può vedere che mostra la riga in cui è check_output()stato chiamato, ma il messaggio di eccezione ora include la riga di comando.


1
Da dove viene ex.strerror? Non riesco a trovare alcun hit rilevante per quello nei documenti di Python. Non dovrebbe essere str(ex)?
Henrik Heimbuerger,

1
IOErrorè derivato da EnvironmentError@hheimbuerger, che fornisce gli attributi errornoe strerror.
Don Kirkby,

Come farei per catturare un oggetto arbitrario Error, ad esempio ValueError, in una RuntimeErrorcattura Exception? Se riproduco la tua risposta per questo caso, lo stacktrace viene perso.
Karl Richter,

Non sono sicuro di quello che stai chiedendo, @karl. Puoi pubblicare un campione in una nuova domanda e quindi collegarti ad esso da qui?
Don Kirkby,

Ho modificato il mio duplicato della domanda del PO a stackoverflow.com/questions/23157766/... con una clearification tenendo conto la risposta direttamente. Dovremmo discutere lì :)
Karl Richter,

12

In Python 3.x :

raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)

o semplicemente

except Exception:
    raise MyException()

che si propaga MyExceptionma stampa entrambe le eccezioni se non verrà gestito.

In Python 2.x :

raise Exception, 'Failed to process file ' + filePath, e

È possibile impedire la stampa di entrambe le eccezioni uccidendo l' __context__attributo. Qui scrivo un gestore di contesto che lo usa per catturare e modificare al volo la tua eccezione: (vedi http://docs.python.org/3.1/library/stdtypes.html per la spiegazione di come funzionano)

try: # Wrap the whole program into the block that will kill __context__.

    class Catcher(Exception):
        '''This context manager reraises an exception under a different name.'''

        def __init__(self, name):
            super().__init__('Failed to process code in {!r}'.format(name))

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is not None:
                self.__traceback__ = exc_tb
                raise self

    ...


    with Catcher('class definition'):
        class a:
            def spam(self):
                # not really pass, but you get the idea
                pass

            lut = [1,
                   3,
                   17,
                   [12,34],
                   5,
                   _spam]


        assert a().lut[-1] == a.spam

    ...


except Catcher as e:
    e.__context__ = None
    raise

4
TypeError: raise: arg 3 deve essere un traceback o None
Glenn Maynard,

Siamo spiacenti, ho fatto un errore, in qualche modo ho pensato che accetta anche le eccezioni e ottiene automaticamente il loro attributo traceback. Secondo docs.python.org/3.1/reference/… , questo dovrebbe essere e .__ traceback__
ilya n.

1
@ilyan .: Python 2 non ha e.__traceback__attributo!
Jan Hudec,

5

Non penso che tu possa farlo in Python 2.x, ma qualcosa di simile a questa funzionalità fa parte di Python 3. Da PEP 3134 :

Nell'attuale implementazione di Python, le eccezioni sono composte da tre parti: il tipo, il valore e il traceback. Il modulo 'sys', espone l'eccezione corrente in tre variabili parallele, exc_type, exc_value ed exc_traceback, la funzione sys.exc_info () restituisce una tupla di queste tre parti e l'istruzione 'raise' ha una forma a tre argomenti che accetta queste tre parti. Per manipolare le eccezioni spesso è necessario passare queste tre cose in parallelo, che può essere noioso e soggetto a errori. Inoltre, l'istruzione 'tranne' può fornire solo l'accesso al valore, non al traceback. L'aggiunta dell'attributo " traceback " ai valori delle eccezioni rende accessibili tutte le informazioni sulle eccezioni da un'unica posizione.

Confronto con C #:

Le eccezioni in C # contengono una proprietà "InnerException" di sola lettura che può puntare a un'altra eccezione. La sua documentazione [10] afferma che "Quando viene generata un'eccezione X come risultato diretto di una precedente eccezione Y, la proprietà InnerException di X dovrebbe contenere un riferimento a Y." Questa proprietà non viene impostata automaticamente dalla VM; piuttosto, tutti i costruttori di eccezioni accettano un argomento opzionale "innerException" per impostarlo esplicitamente. L' attributo ' causa ' soddisfa lo stesso scopo di InnerException, ma questa PEP propone una nuova forma di 'rilancio' piuttosto che estendere i costruttori di tutte le eccezioni. C # fornisce anche un metodo GetBaseException che passa direttamente alla fine della catena InnerException;

Nota anche che Java, Ruby e Perl 5 non supportano neanche questo tipo di cose. Citando di nuovo:

Per quanto riguarda le altre lingue, Java e Ruby scartano entrambe l'eccezione originale quando si verifica un'altra eccezione in una clausola 'catch' / 'rescue' o 'finally' / 'sure'. Perl 5 non ha una gestione delle eccezioni strutturata integrata. Per Perl 6, il numero RFC 88 [9] propone un meccanismo di eccezione che mantiene implicitamente le eccezioni concatenate in un array chiamato @@.


Ma, naturalmente, in Perl5 puoi semplicemente dire "confessa qq {OH NOES! $ @}" E non perdere la traccia dello stack dell'altra eccezione. Oppure puoi implementare il tuo tipo che mantiene l'eccezione.
jrockway,

4

Per la massima compatibilità tra Python 2 e 3, è possibile utilizzare raise_fromnella sixlibreria. https://six.readthedocs.io/#six.raise_from . Ecco il tuo esempio (leggermente modificato per chiarezza):

import six

try:
  ProcessFile(filePath)
except Exception as e:
  six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)

3

È possibile utilizzare la mia classe CausedException per concatenare le eccezioni in Python 2.x (e anche in Python 3 può essere utile nel caso in cui si desideri fornire più di un'eccezione rilevata come causa di un'eccezione appena sollevata). Forse ti può aiutare.


2

Forse potresti prendere le informazioni pertinenti e passarle? Sto pensando a qualcosa del tipo:

import traceback
import sys
import StringIO

class ApplicationError:
    def __init__(self, value, e):
        s = StringIO.StringIO()
        traceback.print_exc(file=s)
        self.value = (value, s.getvalue())

    def __str__(self):
        return repr(self.value)

try:
    try:
        a = 1/0
    except Exception, e:
        raise ApplicationError("Failed to process file", e)
except Exception, e:
    print e

2

assumendo:

  • hai bisogno di una soluzione, che funziona per Python 2 (per Python 3 puro vedi la raise ... fromsoluzione)
  • voglio solo arricchire il messaggio di errore, ad esempio fornendo un contesto aggiuntivo
  • è necessaria la traccia dello stack completo

puoi utilizzare una semplice soluzione dai documenti https://docs.python.org/3/tutorial/errors.html#raising-exceptions :

try:
    raise NameError('HiThere')
except NameError:
    print 'An exception flew by!' # print or log, provide details about context
    raise # reraise the original exception, keeping full stack trace

Il risultato:

An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

Sembra che il pezzo chiave sia la parola chiave "alzare" semplificata che si trova da sola. Ciò aumenterà nuovamente l'eccezione nel blocco tranne.


Questa è la soluzione compatibile con Python 2 e 3! Grazie!
Andy Chase,

Penso che l'idea fosse quella di sollevare un diverso tipo di eccezione.
Tim Ludwinski,

2
Questa non è una catena di eccezioni nidificate, sta solo sollevando un'altra eccezione
Karl Richter,

Questa è la migliore soluzione python 2, se hai solo bisogno di arricchire il messaggio di eccezione e avere la traccia dello stack completo!
geekQ,

Qual è la differenza tra l'utilizzo di rilancio e rilancio da
variabile
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.