Come posso sollevare la stessa eccezione con un messaggio personalizzato in Python?


146

Ho questo tryblocco nel mio codice:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise ValueError(errmsg)

A rigor di termini, in realtà sto sollevando un altro ValueError , non quello ValueErrorlanciato da do_something...(), che viene indicato come errin questo caso. Come allego un messaggio personalizzato err? Io cerco il seguente codice ma non riesce a causa di errun ValueError esempio , non essendo callable:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)

13
@Hamish, allegare ulteriori informazioni e sollevare nuove eccezioni può essere molto utile durante il debug.
Johan Lundberg,

@Johan Assolutamente - ed è a questo che serve uno stack. Non riesco a capire perché modifichi il messaggio di errore esistente invece di generare un nuovo errore.
Hamish,

@Hamish. Sicuro ma puoi aggiungere altre cose. Per la tua domanda, dai un'occhiata alla mia risposta e all'esempio di UnicodeDecodeError. Se hai commenti su questo, forse commenta invece la mia risposta.
Johan Lundberg,


1
@Kit è il 2020 e python 3 è ovunque. Perché non cambiare la risposta accettata alla risposta di Ben :-)
mit

Risposte:


87

Aggiornamento: per Python 3, controlla la risposta di Ben


Per allegare un messaggio all'eccezione corrente e rilanciarlo: (il tentativo esterno / tranne è solo per mostrare l'effetto)

Per python 2.x dove x> = 6:

try:
    try:
      raise ValueError  # something bad...
    except ValueError as err:
      err.message=err.message+" hello"
      raise              # re-raise current exception
except ValueError as e:
    print(" got error of type "+ str(type(e))+" with message " +e.message)

Questo sarà anche fare la cosa giusta , se errè derivato da ValueError. Per esempio UnicodeDecodeError.

Nota che puoi aggiungere quello che ti piace err. Per esempio err.problematic_array=[1,2,3].


Modifica: @Ducan indica in un commento che quanto sopra non funziona con Python 3 poiché .messagenon è un membro ValueError. Invece potresti usare questo (valido Python 2.6 o successivo o 3.x):

try:
    try:
      raise ValueError
    except ValueError as err:
       if not err.args: 
           err.args=('',)
       err.args = err.args + ("hello",)
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e.args))

Edit2:

A seconda di quale sia lo scopo, puoi anche scegliere di aggiungere ulteriori informazioni con il tuo nome di variabile. Per entrambi python2 e python3:

try:
    try:
      raise ValueError
    except ValueError as err:
       err.extra_info = "hello"
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e))
    if 'extra_info' in dir(e):
       print e.extra_info

9
Dato che hai tentato di utilizzare la gestione delle eccezioni in stile Python 3 e print, dovresti probabilmente notare che il tuo codice non funziona in Python 3.x poiché non ci sono messageattributi sulle eccezioni. err.args = (err.args[0] + " hello",) + err.args[1:]potrebbe funzionare in modo più affidabile (e quindi convertirlo in una stringa per ottenere il messaggio).
Duncan,

1
Sfortunatamente non esiste alcuna garanzia che args [0] sia un tipo di stringa che rappresenta un messaggio di errore: "La tupla di argomenti fornita al costruttore dell'eccezione. Alcune eccezioni integrate (come IOError) prevedono un certo numero di argomenti e assegnano un significato speciale a gli elementi di questa tupla, mentre altri sono generalmente chiamati solo con una singola stringa che dà un messaggio di errore ". Quindi il codice non funzionerà arg [0] non è un messaggio di errore (potrebbe essere un int o potrebbe essere una stringa che rappresenta un nome di file).
Trento,

1
@ Taras, interessante. Hai un riferimento su questo? Quindi aggiungerei a un membro completamente nuovo: err.my_own_extra_info. O incapsulare tutto nella mia eccezione mantenendo le informazioni nuove e originali.
Johan Lundberg,

2
Un vero esempio di quando args [0] non è un messaggio di errore - docs.python.org/2/library/exceptions.html - "exception EnvironmentError La classe di base per le eccezioni che possono verificarsi al di fuori del sistema Python: IOError, OSError. Quando vengono create eccezioni di questo tipo con una tupla 2, il primo elemento è disponibile sull'attributo errno dell'istanza (si presume che sia un numero di errore) e il secondo elemento è disponibile sull'attributo strerror (di solito è associato messaggio di errore). La stessa tupla è disponibile anche sull'attributo args. "
Trento,

2
Non lo capisco affatto. L'unico motivo per cui l'impostazione .messagedell'attributo fa qualcosa qui è che questo attributo viene esplicitamente stampato. Se si dovesse alzare l'eccezione senza prendere e la stampa, si potrebbe non vedere l' .messageattributo di fare qualcosa di utile.
DanielSank,

170

Se sei abbastanza fortunato da supportare solo Python 3.x, questo diventa davvero una cosa di bellezza :)

sollevare da

Siamo in grado di concatenare le eccezioni utilizzando raise da .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks') from e

In questo caso, l'eccezione rilevata dal chiamante ha il numero di linea del luogo in cui viene sollevata la nostra eccezione.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

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

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks') from e
Exception: Smelly socks

Si noti che l'eccezione inferiore ha solo lo stacktrace da cui è stata sollevata la nostra eccezione. Il chiamante potrebbe comunque ottenere l'eccezione originale accedendo __cause__all'attributo dell'eccezione rilevata.

with_traceback

Oppure puoi usare with_traceback .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks').with_traceback(e.__traceback__)

Utilizzando questo modulo, l'eccezione rilevata dal chiamante ha il traceback dal punto in cui si è verificato l'errore originale.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks').with_traceback(e.__traceback__)
  File "test.py", line 2, in <module>
    1 / 0
Exception: Smelly socks

Si noti che l'eccezione in basso ha la riga in cui è stata eseguita la divisione non valida nonché la riga in cui è stata effettuata la re-inizializzazione dell'eccezione.


2
È possibile aggiungere un messaggio personalizzato a un'eccezione senza il traceback aggiuntivo? Ad esempio, può raise Exception('Smelly socks') from eessere modificato per aggiungere semplicemente "calzini puzzolenti" come commento al traceback originale anziché introdurre un nuovo traceback a parte.
joelostblom,

Questo è il comportamento che otterrai dalla risposta di Johan Lundberg
Ben

3
è davvero adorabile. Grazie.
allanberry,

4
Ri-sollevare una nuova eccezione o una catena che genera eccezioni con nuovi messaggi crea più confusione del necessario in molti casi. Di per sé le eccezioni sono complesse da gestire. Una strategia migliore è semplicemente aggiungere il tuo messaggio all'argomento dell'eccezione originale, se possibile, come in err.args + = ("message",) e rilanciare il messaggio di eccezione. Il traceback potrebbe non portarti ai numeri di riga in cui è stata rilevata l'eccezione, ma ti porterà sicuramente al punto in cui si è verificata l'eccezione.
user-asterix il

2
Puoi anche sopprimere esplicitamente la visualizzazione della catena delle eccezioni specificando None nella clausola from:raise RuntimeError("Something bad happened") from None
pfabri

9
try:
    try:
        int('a')
    except ValueError as e:
        raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
    print err

stampe:

There is a problem: invalid literal for int() with base 10: 'a'

1
Mi chiedevo se ci fosse un idioma Python per quello che sto cercando di fare, oltre a sollevare un'altra istanza.
Kit

@Kit - Lo chiamerei 'ri-sollevare un'eccezione': docs.python.org/reference/simple_stmts.html#raise
eumiro

1
@eumiro, No, stai facendo una nuova eccezione. Vedi la mia risposta Dal tuo link: "... ma dovrebbe essere preferito rilanciare senza espressioni se l'eccezione da ri-sollevare era l'eccezione attiva più recente nell'ambito attuale."
Johan Lundberg,

3
@JohanLundberg - raisesenza parametri è in aumento. Se OP desidera aggiungere un messaggio, deve sollevare una nuova eccezione e può riutilizzare il messaggio / tipo dell'eccezione originale.
eumiro,

2
Se vuoi aggiungere un messaggio non puoi creare un nuovo messaggio da zero lanciando "ValueError". In questo modo, distruggi le informazioni sottostanti di che tipo di ValueError è (simile alla suddivisione in C ++). Da ri-lanciare la stessa eccezione con raise senza argomenti, si passa l'oggetto originale con quel tipo specifico corretto (derivato da ValueError).
Johan Lundberg,

9

Sembra che tutte le risposte aggiungano informazioni a e.args [0], alterando così il messaggio di errore esistente. C'è invece un aspetto negativo nell'estensione della tupla args? Penso che il possibile lato positivo sia che puoi lasciare solo il messaggio di errore originale nei casi in cui è necessaria l'analisi di quella stringa; e potresti aggiungere più elementi alla tupla se la tua gestione degli errori personalizzata producesse diversi messaggi o codici di errore, per i casi in cui il traceback verrebbe analizzato a livello di codice (come tramite uno strumento di monitoraggio del sistema).

## Approach #1, if the exception may not be derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args = (e.args if e.args else tuple()) + ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

o

## Approach #2, if the exception is always derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args += ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

Riesci a vedere un aspetto negativo di questo approccio?


La mia risposta precedente non modifica e.args [0].
Johan Lundberg,

4

Questa è la funzione che uso per modificare il messaggio di eccezione in Python 2.7 e 3.x preservando il traceback originale. Richiedesix

def reraise_modify(caught_exc, append_msg, prepend=False):
    """Append message to exception while preserving attributes.

    Preserves exception class, and exception traceback.

    Note:
        This function needs to be called inside an except because
        `sys.exc_info()` requires the exception context.

    Args:
        caught_exc(Exception): The caught exception object
        append_msg(str): The message to append to the caught exception
        prepend(bool): If True prepend the message to args instead of appending

    Returns:
        None

    Side Effects:
        Re-raises the exception with the preserved data / trace but
        modified message
    """
    ExceptClass = type(caught_exc)
    # Keep old traceback
    traceback = sys.exc_info()[2]
    if not caught_exc.args:
        # If no args, create our own tuple
        arg_list = [append_msg]
    else:
        # Take the last arg
        # If it is a string
        # append your message.
        # Otherwise append it to the
        # arg list(Not as pretty)
        arg_list = list(caught_exc.args[:-1])
        last_arg = caught_exc.args[-1]
        if isinstance(last_arg, str):
            if prepend:
                arg_list.append(append_msg + last_arg)
            else:
                arg_list.append(last_arg + append_msg)
        else:
            arg_list += [last_arg, append_msg]
    caught_exc.args = tuple(arg_list)
    six.reraise(ExceptClass,
                caught_exc,
                traceback)

Questa è una delle poche risposte che risponde effettivamente alla domanda originale. Quindi va bene. Purtroppo, Python 2.7 (e quindi six) è EOL. Quindi, questo è male. Anche se tecnicamente si potrebbe ancora usare sixnel 2020, non c'è alcun vantaggio tangibile nel farlo. Le soluzioni Pure-Python 3.x sono ampiamente preferibili ora.
Cecil Curry

3

Questo modello di codice dovrebbe consentire di sollevare un'eccezione con un messaggio personalizzato.

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")

4
Questo non conserva la traccia dello stack.
visita

L'interrogante non specifica che la traccia dello stack deve essere preservata.
shrewmouse,

Non essere intenzionalmente ottuso. La domanda originale alla lettera era: "Come faccio a sollevare lo stesso Exceptioncon un messaggio personalizzato in Python?" Questa mancata risposta solleva una nuova eccezione e quindi non risponde affatto alla domanda originale. Questo è il motivo per cui non possiamo avere cose buone.
Cecil Curry

3

O solleva la nuova eccezione con il tuo messaggio di errore utilizzando

raise Exception('your error message')

o

raise ValueError('your error message')

nel punto in cui si desidera sollevarlo O allegare (sostituire) il messaggio di errore nell'eccezione corrente usando 'from' (supportato solo da Python 3.x):

except ValueError as e:
  raise ValueError('your message') from e

Thanx, @gberger, l'approccio 'from e' in realtà non è supportato da python 2.x
Alexey Antonenko,

Con sorpresa di nessuno, questo non riesce a rispondere alla domanda reale.
Cecil Curry

2

La risposta attuale non ha funzionato bene per me, se l'eccezione non viene catturata nuovamente il messaggio allegato non viene visualizzato.

Ma fare come sotto entrambi mantiene la traccia e mostra il messaggio allegato indipendentemente dal fatto che l'eccezione venga catturata nuovamente o meno.

try:
  raise ValueError("Original message")
except ValueError as err:
  t, v, tb = sys.exc_info()
  raise t, ValueError(err.message + " Appended Info"), tb

(Ho usato Python 2.7, non l'ho provato in Python 3)


2

Le eccezioni integrate in Python 3 hanno il strerrorcampo:

except ValueError as err:
  err.strerror = "New error message"
  raise err

Questo non sembra funzionare. Ti stai perdendo qualcosa?
MasayoMusic,

La strerrorvariabile di istanza è specifica OSErrordell'eccezione . Poiché la maggior parte delle ValueErroreccezioni garantisce di non definire questa variabile, questa non-soluzione genera in genere eccezioni non leggibili dall'uomo ed è quindi attivamente dannosa. lol, fratello.
Cecil Curry

1

Funziona solo con Python 3 . È possibile modificare gli argomenti originali dell'eccezione e aggiungere i propri argomenti.

Un'eccezione ricorda gli argomenti con cui è stata creata. Presumo che sia così che tu possa modificare l'eccezione.

Nella funzione reraiseanteponiamo gli argomenti originali dell'eccezione con qualsiasi nuovo argomento che desideriamo (come un messaggio). Infine, ri-solleviamo l'eccezione preservando la cronologia del ritorno.

def reraise(e, *args):
  '''re-raise an exception with extra arguments
  :param e: The exception to reraise
  :param args: Extra args to add to the exception
  '''

  # e.args is a tuple of arguments that the exception with instantiated with.
  #
  e.args = args + e.args

  # Recreate the expection and preserve the traceback info so thta we can see 
  # where this exception originated.
  #
  raise e.with_traceback(e.__traceback__)   


def bad():
  raise ValueError('bad')

def very():
  try:
    bad()
  except Exception as e:
    reraise(e, 'very')

def very_very():
  try:
    very()
  except Exception as e:
    reraise(e, 'very')

very_very()

produzione

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    very_very()
  File "main.py", line 30, in very_very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 28, in very_very
    very()
  File "main.py", line 24, in very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 22, in very
    bad()
  File "main.py", line 18, in bad
    raise ValueError('bad')
ValueError: ('very', 'very', 'bad')

1
Di gran lunga la migliore risposta. Questa è l'unica risposta che risponde alla domanda originale, conserva il traceback originale ed è pure-Python 3.x. Oggetti di scena folli anche per suonare quel tamburo meme "molto, molto cattivo". L'umorismo è indubbiamente una buona cosa, specialmente in risposte tecniche altrimenti secche come questa. Bravo!
Cecil Curry

0

Nessuna delle soluzioni sopra ha fatto esattamente quello che volevo, che era quello di aggiungere alcune informazioni alla prima parte del messaggio di errore, cioè volevo che i miei utenti vedessero prima il mio messaggio personalizzato.

Questo ha funzionato per me:

exception_raised = False
try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    message = str(e)
    exception_raised = True

if exception_raised:
    message_to_prepend = "Custom text"
    raise ValueError(message_to_prepend + message)

-4

se si desidera personalizzare il tipo di errore, una cosa semplice da fare è definire una classe di errore basata su ValueError.


come sarebbe d'aiuto in questo caso?
Johan Lundberg,

... immagina di pensare davvero che questa fosse una risposta valida.
Cecil Curry
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.