2009-03-30 10 views
72

Sto scrivendo un modulo e voglio avere una gerarchia di eccezioni unificate per le eccezioni che può sollevare (ad esempio ereditando da una classe astratta FooError per tutti gli foo eccezioni specifiche del modulo). Ciò consente agli utenti del modulo di rilevare quelle particolari eccezioni e gestirle distintamente, se necessario. Ma molte delle eccezioni sollevate dal modulo vengono sollevate a causa di qualche altra eccezione; per esempio. errore in qualche attività a causa di un OSError su un file.Rilanciare l'eccezione con un tipo e un messaggio diversi, mantenendo le informazioni esistenti

Quello che mi serve è "avvolgere" l'eccezione rilevata in modo che abbia un tipo e un messaggio diversi, in modo che le informazioni siano disponibili più in alto nella gerarchia di propagazione da qualunque sia l'eccezione. Ma non voglio perdere il tipo, il messaggio e lo stack esistenti; sono tutte informazioni utili per qualcuno che cerca di eseguire il debug del problema. Un gestore di eccezioni di primo livello non va bene, dal momento che sto tentando di decorare l'eccezione prima che raggiunga lo stack di propagazione e il gestore di livello superiore sia troppo tardi.

Questo è in parte risolto derivando miei tipi di eccezione specifici s' modulo foo dal tipo esistente (es class FooPermissionError(OSError, FooError)), ma che non lo rende più facile per avvolgere l'istanza eccezione esistente in un nuovo tipo, né modificare la Messaggio.

“concatenamento delle eccezioni e di traceback embedded” PEP 3134 di Python discute un cambiamento accettato in Python 3.0 per “concatenamento” oggetti di eccezione, per indicare che una nuova eccezione è stata sollevata durante la manipolazione di un'eccezione esistente.

Quello che sto cercando di fare è relativo: ho bisogno che funzioni anche nelle versioni precedenti di Python, e non ne ho bisogno per il concatenamento, ma solo per il polimorfismo. Qual è il modo giusto per farlo?

+0

eccezioni già sono completamente polimorfica - sono tutte sottoclassi di Eccezione. Cosa stai cercando di fare? "Messaggio diverso" è abbastanza banale con un gestore di eccezioni di primo livello. Perché stai cambiando la classe? –

+0

Come spiegato nella domanda (ora, grazie per il tuo commento): Sto cercando di decorare un'eccezione che ho catturato, in modo che possa propagarsi più in alto con più informazioni ma senza perdere nulla. Un gestore di livello superiore è troppo tardi. – bignose

+0

Si prega di dare un'occhiata alla mia [classe CausedException] (http://code.activestate.com/recipes/578252-python-exception-chains-or-trees/?in=user-4182236) che può fare ciò che si desidera in Python 2.x. Inoltre in Python 3 può essere utile nel caso in cui si desideri fornire più di un'eccezione originale come causa della propria eccezione. Forse si adatta alle tue esigenze. – Alfe

risposta

79

Python 3 introdotto eccezione concatenamento (come descritto in PEP 3134).Questo permette di sollevare un'eccezione, citando un'eccezione esistente come la “causa”:

try: 
    frobnicate() 
except KeyError as exc: 
    raise ValueError("Bad grape") from exc 

L'eccezione catturato diventa così parte (è la “causa”), la nuova eccezione, ed è disponibile per qualsiasi codice cattura la nuova eccezione


In Python 2, sembra questo caso di utilizzo non ha buona risposta (come descritto da Ian Bicking e Ned Batchelder). Bummer.

+2

Does not Ian Bicking descrive la mia soluzione? Mi dispiace di aver dato una risposta così stupida, ma è strano che questo sia stato accettato. –

+0

@bignose: Grazie per aver menzionato questo. Ho perso questo cambiamento in Python3. Il concatenamento eccezionale farà risparmiare molto dolore. –

+1

@bignose Hai ottenuto il mio punto non solo dall'essere corretto, ma per l'uso di "frobnicate" :) –

8

È possibile creare il proprio tipo di eccezione che estende lo whichever exception rilevato.

class NewException(CaughtException): 
    def __init__(self, caught): 
     self.caught = caught 

try: 
    ... 
except CaughtException as e: 
    ... 
    raise NewException(e) 

Ma la maggior parte del tempo, penso che sarebbe più semplice per catturare l'eccezione, gestire, e sia raise l'eccezione originale (e preservare il traceback) oppure raise NewException(). Se dovessi chiamare il tuo codice e ho ricevuto una delle tue eccezioni personalizzate, mi aspetto che il tuo codice abbia già gestito qualsiasi eccezione tu abbia dovuto intercettare. Quindi non ho bisogno di accedervi da solo.

Modifica: ho trovato this analysis di modi per lanciare la propria eccezione e mantenere l'eccezione originale. Nessuna soluzione carina.

+1

Il caso d'uso che ho descritto non è per * gestire * l'eccezione; riguarda specificamente * non * la gestione, ma l'aggiunta di alcune informazioni aggiuntive (una classe aggiuntiva e un nuovo messaggio) in modo che possa essere gestita più in alto nello stack di chiamate. – bignose

27

È possibile utilizzare sys.exc_info() per ottenere il traceback e generare la nuova eccezione con detto traceback (come menzionato dalla PEP). Se si desidera conservare il vecchio tipo e il messaggio, è possibile farlo sull'eccezione, ma ciò è utile solo se ciò che rileva l'eccezione lo cerca.

Per esempio

import sys 

def failure(): 
    try: 1/0 
    except ZeroDivisionError, e: 
     type, value, traceback = sys.exc_info() 
     raise ValueError, ("You did something wrong!", type, value), traceback 

Naturalmente, questo non è poi così utile. Se lo fosse, non avremmo bisogno di quel PEP. Non raccomanderei di farlo.

+0

Devin, si memorizza un riferimento al traceback lì, non dovresti eliminare esplicitamente tale riferimento? – Arafangion

+2

Non ho archiviato nulla, ho lasciato traceback come variabile locale che presumibilmente non rientra nell'ambito. Sì, è concepibile che non lo sia, ma se si sollevano eccezioni come quella nell'ambito globale piuttosto che all'interno delle funzioni, si hanno problemi più grandi. Se il tuo reclamo è solo che potrebbe essere eseguito in un ambito globale, la soluzione corretta non è quella di aggiungere un boilerplate irrilevante che deve essere spiegato e non è rilevante per il 99% degli usi, ma per riscrivere la soluzione in modo tale che nulla del genere è necessario mentre sembra che nulla sia diverso, come ho fatto ora. –

+3

Arafangion potrebbe riferirsi ad un avvertimento nella [documentazione di Python per 'sys.exc_info()'] (http://docs.python.org/library/sys.html#sys.exc_info), @Devin. Dice "Assegnare il valore di ritorno traceback a una variabile locale in una funzione che gestisce un'eccezione causerà un riferimento circolare." Tuttavia, una nota successiva dice che da Python 2.2, il ciclo può essere ripulito, ma è più efficiente evitarlo. –

-2

La soluzione più straighforward alle vostre esigenze dovrebbe essere questo:

try: 
    upload(file_id) 
except Exception as upload_error: 
    error_msg = "Your upload failed! File: " + file_id 
    raise RuntimeError(error_msg, upload_error) 

In questo modo è possibile poi stampare il messaggio e l'errore specifico throwed dalla funzione di upload

+1

Che cattura e quindi * getta via * l'oggetto eccezione, quindi no, non soddisfa il bisogni della domanda La domanda chiede come mantenere * l'eccezione esistente e consentire la stessa eccezione, con tutte le informazioni utili che contiene, per continuare a propagare lo stack. – bignose