Come rilanciare un'eccezione nei blocchi try / tranne annidati?


109

So che se voglio rilanciare un'eccezione, lo uso semplicemente raisesenza argomenti nel rispettivo exceptblocco. Ma data un'espressione annidata come

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # I'd like to raise the SomeError as if plan_B()
                 # didn't raise the AlsoFailsError

come posso rilanciare SomeErrorsenza rompere la traccia dello stack? raisesolo in questo caso rialzerebbe il più recente AlsoFailsError. O come posso rifattorizzare il mio codice per evitare questo problema?


2
Hai provato a inserire plan_Bun'altra funzione che ritorna Truein caso di successo e Falsesu eccezione? Quindi il exceptblocco esterno potrebbe essere soloif not try_plan_B(): raise
Drew McGowen

@DrewMcGowen Sfortunatamente il caso più realistico è che questo è all'interno di una funzione che accetta oggetti arbitrari arge proverei a chiamare arg.plan_B()che potrebbe sollevare una AttributeErrorcausa per argnon fornire un piano B
Tobias Kienzler


@ Paco Grazie, lo farò (anche se una risposta mostra già un modo più semplice)
Tobias Kienzler

@DrewMcGowen Ho scritto una risposta basata sul tuo commento , che però sembra meno pitonica della risposta di user4815162342 . Ma questo è dovuto al fatto che volevo avere anche un valore di ritorno e consentire plan_Bdi sollevare eccezioni
Tobias Kienzler

Risposte:


131

A partire da Python 3 il traceback è memorizzato nell'eccezione, quindi un semplice raise efarà la cosa (per lo più) giusta:

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # or raise e from None - see below

Il traceback prodotto includerà un avviso aggiuntivo che si è SomeErrorverificato durante la manipolazione AlsoFailsError(perché si raise etrova all'interno except AlsoFailsError). Questo è fuorviante perché ciò che è realmente accaduto è il contrario: l'abbiamo incontrato AlsoFailsErrore gestito, mentre cercavamo di riprenderci SomeError. Per ottenere un traceback che non include AlsoFailsError, sostituire raise econ raise e from None.

In Python 2 dovresti memorizzare il tipo di eccezione, il valore e il traceback in variabili locali e utilizzare la forma a tre argomenti diraise :

try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        raise t, v, tb

Perfetto, è quello che ho trovato anche qui , grazie! Anche se il suggerimento è raise self.exc_info[1], None, self.exc_info[2]dopo self.exc_info = sys.exc_info()- mettersi [1]in prima posizione per qualche motivo
Tobias Kienzler il

3
@TobiasKienzler raise t, None, tbperderà il valore dell'eccezione e forzerà raisea reistanziarla dal tipo, fornendoti un valore di eccezione meno specifico (o semplicemente errato). Ad esempio, se l'eccezione sollevata è KeyError("some-key"), si solleverà nuovamente KeyError()e ometterà l'esatta chiave mancante dal traceback.
user4815162342

3
@TobiasKienzler Dovrebbe ancora essere possibile esprimerlo in Python 3 come raise v.with_traceback(tb). (Il tuo commento dice altrettanto, tranne che propone di reistituire il valore.)
user4815162342

2
Inoltre, l'avviso rosso di non memorizzare sys.exc_info()in una variabile locale aveva senso prima di Python 2.0 (rilasciato 13 anni fa), ma oggi rasenta il ridicolo. Il Python moderno sarebbe quasi inutile senza il raccoglitore di cicli, poiché ogni libreria Python non banale crea cicli senza pause e dipende dalla loro corretta pulizia.
user4815162342

1
@ user4815162342 Puoi eliminare l'errore annidato "si è verificato un altro errore" scrivendo "raise e from None".
Matthias Urlichs

19

Anche se la soluzione accettata è giusta, è bene puntare alla libreria Six che ha una soluzione Python 2 + 3, usando six.reraise.

sei. reraise ( exc_type , exc_value , exc_traceback = Nessuno)

Reraise un'eccezione, possibilmente con un diverso traceback. [...]

Quindi puoi scrivere:

import six


try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        six.reraise(t, v, tb)

1
Buon punto: parlare di Six puoi anche usare six.raise_fromse vuoi includere informazioni che plan_B()non sono riuscite.
Tobias Kienzler

1
@TobiasKienzler: Penso che sia un utilizzo diverso: six.raise_fromquando crei una nuova eccezione collegata a una precedente, non rilanci nuovamente , quindi la traccia a ritroso è diversa.
Laurent LAPORTE

1
Il mio punto esattamente - se hai reraisesolo l'impressione di something()lanciare SomeError, se raise_fromsai anche che questo ha causato plan_B()l'esecuzione ma il lancio del file AlsoFailsError. Quindi dipende dal caso d'uso. Penso raise_fromche renderà più facile il debug
Tobias Kienzler

9

Come da suggerimento di Drew McGowen , ma occupandosi di un caso generale (dove sè presente un valore di ritorno ), ecco un'alternativa alla risposta di user4815162342 :

try:
    s = something()
except SomeError as e:
    def wrapped_plan_B():
        try:
            return False, plan_B()
        except:
            return True, None
    failed, s = wrapped_plan_B()
    if failed:
        raise

1
La cosa bella di questo approccio è che funziona invariato in Python 2 e 3.
user4815162342

2
@ user4815162342 Buon punto :) Anche se nel frattempo in Python3 lo prenderei in considerazione raise from, quindi la traccia dello stack mi permetterebbe anche di vedere il piano B fallito. Che può essere emulato in Python 2 tra l'altro.
Tobias Kienzler

5

Python 3.5+ allega comunque le informazioni di traceback all'errore, quindi non è più necessario salvarle separatamente.

>>> def f():
...   try:
...     raise SyntaxError
...   except Exception as e:
...     err = e
...     try:
...       raise AttributeError
...     except Exception as e1:
...       raise err from None
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in f
  File "<stdin>", line 3, in f
SyntaxError: None
>>> 

2
La domanda riguarda un'altra eccezione che si verifica durante il except. Ma hai ragione, quando sostituisco err = econ, diciamo, raise AttributeErrorottieni prima la SyntaxErrortraccia dello stack, seguita da a During handling of the above exception, another exception occurred:e la AttributeErrortraccia dello stack. Buono a sapersi, anche se sfortunatamente non si può fare affidamento sull'installazione di 3.5+. PS: ff verstehen nicht-Deutsche vermutlich nicht;)
Tobias Kienzler

OK, quindi ho cambiato l'esempio per sollevare un'altra eccezione, che (come la domanda originale richiesta) viene ignorata quando sollevo nuovamente la prima.
Matthias Urlichs

3
@TobiasKienzler 3.5+ (in cui l'ho cambiato) sembra essere un formato riconosciuto a livello mondiale. Era denkst du? ;)
Linusg

@linusg Concordato :)
Tobias Kienzler
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.