¿Cómo provoco la misma excepción con un mensaje personalizado en Python?


145

Tengo este trybloque en mi código:

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

Hablando estrictamente, en realidad estoy planteando otro ValueError , no el ValueErrorarrojado do_something...(), que se conoce como erren este caso. ¿Cómo adjunto un mensaje personalizado err? Intento con el siguiente código pero falla debido a que err, por ValueError ejemplo , no se puede llamar:

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

13
@Hamish, adjuntar información adicional y volver a generar excepciones puede ser muy útil al depurar.
Johan Lundberg

@Johan Absolutamente, y para eso sirve el stacktrace. No puedo entender por qué editaría el mensaje de error existente en lugar de generar un nuevo error.
Hamish

@Hamish. Claro, pero puedes agregar otras cosas. Para su pregunta, eche un vistazo a mi respuesta y al ejemplo de UnicodeDecodeError. Si tiene comentarios sobre eso, quizás comente mi respuesta en su lugar.
Johan Lundberg


1
@Kit es 2020 y Python 3 está en todas partes. ¿Por qué no cambiar la respuesta aceptada a la respuesta de Ben :-)
mit

Respuestas:


88

Actualización: para Python 3, verifique la respuesta de Ben


Para adjuntar un mensaje a la excepción actual y volver a subirlo: (el intento externo / excepto es solo para mostrar el efecto)

Para python 2.x donde 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)

Esto también hará lo correcto si errse deriva de ValueError. Por ejemplo UnicodeDecodeError.

Tenga en cuenta que puede agregar lo que quiera err. Por ejemplo err.problematic_array=[1,2,3].


Editar: @Ducan señala en un comentario que lo anterior no funciona con python 3 ya .messageque no es miembro de ValueError. En cambio, podría usar esto (python válido 2.6 o posterior 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:

Dependiendo de cuál sea el propósito, también puede optar por agregar la información adicional bajo su propio nombre de variable. Para python2 y 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
Dado que se ha esforzado por utilizar el manejo de excepciones al estilo Python 3 print, probablemente debería tener en cuenta que su código no funciona en Python 3.x ya que no hay messageatributos en las excepciones. err.args = (err.args[0] + " hello",) + err.args[1:]puede funcionar de manera más confiable (y luego simplemente convertir a una cadena para obtener el mensaje).
Duncan

1
Desafortunadamente, no hay garantía de que args [0] sea un tipo de cadena que represente un mensaje de error: "La tupla de argumentos dada al constructor de excepciones. Algunas excepciones incorporadas (como IOError) esperan un cierto número de argumentos y asignan un significado especial a los elementos de esta tupla, mientras que otros solo se llaman con una sola cadena que da un mensaje de error ". Por lo tanto, el código no funcionará arg [0] no es un mensaje de error (podría ser un int o una cadena que representa un nombre de archivo).
Trent el

1
@Taras, Interesante. ¿Tienes una referencia sobre eso? Luego agregaría a un miembro completamente nuevo: err.my_own_extra_info. O encapsularlo todo en mi propia excepción manteniendo la información nueva y original.
Johan Lundberg el

2
Un ejemplo real de cuando args [0] no es un mensaje de error - docs.python.org/2/library/exceptions.html - "excepción EnvironmentError La clase base para excepciones que pueden ocurrir fuera del sistema Python: IOError, OSError. Cuando se crean excepciones de este tipo con una tupla de 2, el primer elemento está disponible en el atributo errno de la instancia (se supone que es un número de error), y el segundo elemento está disponible en el atributo strerror (generalmente es el asociado mensaje de error). La tupla misma también está disponible en el atributo args ".
Trent

2
No entiendo esto en absoluto. La única razón por la que el .messageatributo hace algo aquí es que este atributo se imprime explícitamente . Si fuera a plantear la excepción sin capturar e imprimir, no vería que el .messageatributo hace nada útil.
DanielSank

171

Si tienes la suerte de admitir solo Python 3.x, esto realmente se convierte en algo bello :)

elevar desde

Podemos encadenar las excepciones usando raise from .

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

En este caso, la excepción que detectaría su interlocutor tiene el número de línea del lugar donde planteamos nuestra excepción.

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

Observe que la excepción inferior solo tiene el stacktrace desde donde generamos nuestra excepción. La persona que llama aún puede obtener la excepción original accediendo al __cause__atributo de la excepción que detecta.

con_traceback

O puede usar with_traceback .

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

Al usar este formulario, la excepción que su interlocutor detectaría tiene el rastreo desde donde ocurrió el error original.

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

Observe que la excepción inferior tiene la línea donde realizamos la división no válida, así como la línea donde volvemos a plantear la excepción.


1
¿Es posible agregar un mensaje personalizado a una excepción sin el rastreo adicional? Por ejemplo, se raise Exception('Smelly socks') from epuede modificar para agregar simplemente "calcetines malolientes" como un comentario al rastreo original en lugar de introducir un nuevo rastreo propio.
joelostblom

Ese es el comportamiento que obtendrá de la respuesta de Johan Lundberg
Ben

3
Esto es realmente encantador. Gracias.
allanberry

3
Volver a generar una nueva excepción o establecer excepciones en cadena con nuevos mensajes crea más confusión de la necesaria en muchos casos. Por sí solo, las excepciones son complejas de manejar. Una estrategia mejor es simplemente agregar su mensaje al argumento de la excepción original si es posible como en err.args + = ("mensaje",) y volver a generar el mensaje de excepción. Es posible que el rastreo no lo lleve a los números de línea donde se detectó la excepción, pero seguramente lo llevará a donde ocurrió la excepción.
user-asterix

2
También puede suprimir explícitamente la visualización de la cadena de excepción especificando Ninguno en la cláusula from:raise RuntimeError("Something bad happened") from None
pfabri

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

huellas dactilares:

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

1
Me preguntaba si había un idioma de Python para lo que estoy tratando de hacer, aparte de plantear otra instancia.
Kit del

@Kit: lo llamaría 'volver a generar una excepción': docs.python.org/reference/simple_stmts.html#raise
eumiro

1
@eumiro, No, estás haciendo una nueva excepción. Mira mi respuesta. Desde su enlace: "... pero se debería preferir el aumento sin expresiones si la excepción que se va a volver a generar es la excepción activa más reciente en el ámbito actual".
Johan Lundberg

3
@JohanLundberg: raisesin parámetros está volviendo a subir. Si OP desea agregar un mensaje, tiene que generar una nueva excepción y puede reutilizar el mensaje / tipo de la excepción original.
eumiro

2
Si desea agregar un mensaje, no puede crear un nuevo mensaje desde cero lanzando "ValueError". Al hacerlo, destruye la información subyacente de qué tipo de ValueError es (similar a cortar en C ++). Al volver a lanzar la misma excepción con raise sin argumento, pasa el objeto original con ese tipo específico correcto (derivado de ValueError).
Johan Lundberg

9

Parece que todas las respuestas están agregando información a e.args [0], alterando así el mensaje de error existente. ¿Hay alguna desventaja en extender la tupla args en su lugar? Creo que la posible ventaja es que puede dejar solo el mensaje de error original para los casos en que se necesita analizar esa cadena; y podría agregar varios elementos a la tupla si su manejo de errores personalizado produjera varios mensajes o códigos de error, en los casos en que el rastreo se analizaría mediante programación (como a través de una herramienta de monitoreo 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')

¿Puedes ver una desventaja de este enfoque?


Mi respuesta anterior no altera e.args [0].
Johan Lundberg

4

Esta plantilla de código debería permitirle generar una excepción con un mensaje personalizado.

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

3
Esto no conserva el seguimiento de la pila.
plok

El interlocutor no especifica que se conserve el seguimiento de la pila.
musaraña

4

Levante la nueva excepción con su mensaje de error usando

raise Exception('your error message')

o

raise ValueError('your error message')

dentro del lugar donde desea elevarlo O adjuntar (reemplazar) el mensaje de error a la excepción actual usando 'from' (solo se admite Python 3.x):

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

Gracias, @gberger, el enfoque 'desde e' en realidad no es compatible con python 2.x
Alexey Antonenko

3

Esta es la función que uso para modificar el mensaje de excepción en Python 2.7 y 3.x mientras se conserva el rastreo original. Requieresix

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)

3

Las excepciones incorporadas en Python 3 tienen el strerrorcampo:

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

Esto no parece funcionar. ¿Te estás perdiendo algo?
MasayoMusic

2

La respuesta actual no funcionó bien para mí, si la excepción no se vuelve a capturar, el mensaje adjunto no se muestra.

Pero al hacer lo siguiente, ambos mantienen la traza y muestran el mensaje adjunto, independientemente de si la excepción se vuelve a capturar o no.

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

(Usé Python 2.7, no lo he probado en Python 3)


1

Ninguna de las soluciones anteriores hizo exactamente lo que quería, que era agregar información a la primera parte del mensaje de error, es decir, quería que mis usuarios vieran primero mi mensaje personalizado.

Esto funcionó para mí:

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)

0

Esto solo funciona con Python 3 . Puede modificar los argumentos originales de la excepción y agregar sus propios argumentos.

Una excepción recuerda los argumentos con los que se creó. Supongo que esto es para que pueda modificar la excepción.

En la función reraise, anteponemos los argumentos originales de la excepción con cualquier argumento nuevo que queramos (como un mensaje). Finalmente, volvemos a plantear la excepción mientras conservamos el historial de rastreo.

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

salida

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

-3

Si desea personalizar el tipo de error, una cosa simple que puede hacer es definir una clase de error basada en ValueError.


¿Cómo ayudaría eso en este caso?
Johan Lundberg
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.