¿Agregar información a una excepción?

12 minutos de lectura

avatar de usuario
anijhaw

Quiero lograr algo como esto:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

Pero lo que obtengo es:

Traceback..
  IOError('Stuff')

¿Alguna pista sobre cómo lograr esto? ¿Cómo hacerlo tanto en Python 2 como en 3?

  • Mientras busca documentación para la excepción message atributo Encontré esta pregunta SO, BaseException.message en desuso en Python 2.6, lo que parece indicar que ahora se desaconseja su uso (y por qué no está en los documentos).

    – martineau

    19 mayo 2011 a las 19:50

  • lamentablemente, ese enlace ya no parece funcionar.

    –Michael Scott Asato Cuthbert

    08/12/2013 a las 20:55

  • @MichaelScottCuthbert aquí hay una buena alternativa: itmaybeahack.com/book/python-2.6/html/p02/…

    – Niels Keurentjes

    17 de enero de 2014 a las 16:08

  • Aquí hay una muy buena explicación de cuál es el estado del atributo del mensaje y su relación con el atributo args y PEP 352. es del libro gratis Desarrollo de habilidades en Python por Steven F. Lott.

    – martineau

    7 de mayo de 2014 a las 0:20


avatar de usuario
cris

En caso de que haya venido aquí buscando una solución para Python 3 el manual dice:

Al generar una nueva excepción (en lugar de usar un simple raise para volver a generar la excepción que se está manejando actualmente), el contexto de excepción implícita se puede complementar con una causa explícita usando from con raise:

raise new_exc from original_exc

Ejemplo:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

Que se ve así al final:

2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

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

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

Convirtiéndose en un totalmente anodino TypeError en un buen mensaje con sugerencias para una solución sin estropear la Excepción original.

  • Esta es la mejor solución, ya que la excepción resultante apunta a la causa original, proporcione más detalles.

    – JT

    6 de agosto de 2018 a las 21:28

  • ¿Hay alguna solución para que podamos agregar algún mensaje pero aún así no generar una nueva excepción? Me refiero a simplemente extender el mensaje de la instancia de excepción.

    – edcsam

    3 de junio de 2020 a las 1:26


  • Yaa~~ funciona, pero se siente como algo que no se me debe hacer. El mensaje se almacena en e.args, pero eso es una Tupla, por lo que no se puede cambiar. Así que primera copia args en una lista, luego modifíquelo, luego cópielo nuevamente como una Tupla: args = list(e.args) args[0] = 'bar' e.args = tuple(args)

    – Chris

    3 de junio de 2020 a las 9:58


  • @edcSam, encontré que esto funciona para una excepción personalizada: def __init__(self, message): super().__init__("some prefix: " + message)

    –Kevin

    25 de febrero de 2021 a las 21:30

avatar de usuario
martineau

Lo haría así cambiando su tipo en foo() no requerirá también cambiarlo en bar().

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

Actualización 1

Aquí hay una ligera modificación que conserva el rastreo original:

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

Actualización 2

Para Python 3.x, el código de mi primera actualización es sintácticamente incorrecto, además de la idea de tener un message atributo en BaseException estaba retractado en un cambio a PEP 352 el 2012-05-16 (mi primera actualización se publicó el 2012-03-12). Entonces, actualmente, en Python 3.5.2 de todos modos, necesitaría hacer algo en este sentido para preservar el rastreo y no codificar el tipo de excepción en la función bar(). También tenga en cuenta que habrá la línea:

During handling of the above exception, another exception occurred:

en los mensajes de rastreo mostrados.

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

Actualización 3

Un comentarista preguntó si había alguna forma de que funcionara tanto en Python 2 como en 3. Aunque la respuesta podría parecer “No” debido a las diferencias de sintaxis, hay es una forma de evitar eso mediante el uso de una función de ayuda como reraise() en el six módulo adicional. Entonces, si prefiere no usar la biblioteca por alguna razón, a continuación encontrará una versión independiente simplificada.

Tenga en cuenta también que, dado que la excepción se vuelve a plantear dentro del reraise() función, que aparecerá en cualquier rastreo que se genere, pero el resultado final es lo que desea.

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')

  • Eso pierde el backtrace, de alguna manera derrotando el punto de agregar información a una excepción existente. Además, no funciona con excepciones con ctor que toma> 1 argumentos (el tipo es algo que no puede controlar desde el lugar donde detecta la excepción).

    – Václav Slavík

    12 de marzo de 2012 a las 16:09


  • @Václav: Es bastante fácil evitar perder el rastro, como se muestra en la actualización que agregué. Si bien esto todavía no maneja todas las excepciones imaginables, funciona para casos similares a los que se muestran en la pregunta del OP.

    – martineau

    12/03/2012 a las 21:11

  • esto no es bastante Correcto. Si el tipo (e) anula __str__, puede obtener resultados no deseados. También tenga en cuenta que el segundo argumento se pasa al constructor dado por el primer argumento, lo que produce un algo sin sentido type(e)(type(e)(e.message). En tercer lugar, e.message está en desuso a favor de e.args[0].

    – bukzor

    11 de diciembre de 2013 a las 23:18


  • Entonces, ¿no hay una forma portátil que funcione tanto en Python 2 como en 3?

    – Elías Dorneles

    1 de agosto de 2014 a las 17:09

  • @martineau ¿Cuál es el propósito de importar dentro del bloque excepto? ¿Es esto para ahorrar memoria importando solo cuando sea necesario?

    – AllTradesJack

    11 de agosto de 2014 a las 23:54

avatar de usuario
steve howard

Asumiendo que no quiere o no puede modificar foo(), puede hacer esto:

try:
    raise IOError('stuff')
except Exception as e:
    if len(e.args) >= 1:
        e.args = (e.args[0] + ' happens',) + e.args[1:]
    raise

De hecho, esta es la única solución aquí que resuelve el problema en Python 3 sin un mensaje feo y confuso “Durante el manejo de la excepción anterior, ocurrió otra excepción”.

En caso de que la línea de resubida deba agregarse a la traza de la pila, escribir raise e en vez de raise hará el truco.

  • pero en este caso, si la excepción cambia en foo, también tengo que cambiar la barra, ¿verdad?

    – anijhaw

    19 mayo 2011 a las 17:56

  • Si detecta Exception (editado anteriormente), puede detectar cualquier excepción de biblioteca estándar (así como aquellas que heredan de Exception y llaman a Exception.__init__).

    –Steve Howard

    19 mayo 2011 a las 18:01

  • para ser más completo/cooperativo, incluya las otras partes de la tupla original: e.args = ('mynewstr' + e.args[0],) + e.args[1:]

    – Dubslow

    31 de enero de 2018 a las 12:06


  • @nmz787 Este es el mejor solución para Python 3 de hecho. ¿Cuál es exactamente tu error?

    – cristiano

    5 de noviembre de 2018 a las 20:27

  • @Dubslow y martineau Incorporé sus sugerencias en una edición.

    – cristiano

    5 de noviembre de 2018 a las 20:28

avatar de usuario
6EQUJ5

No me gustan todas las respuestas dadas hasta ahora. Todavía son demasiado detallados en mi humilde opinión. Tanto en el código como en la salida del mensaje.

Todo lo que quiero tener es el seguimiento de pila que apunte a la excepción de origen, sin cosas de excepción en el medio, por lo que no se deben crear nuevas excepciones, solo volver a generar el original con todas las importante apilar estados de marco en él, que condujo allí.

Steve Howard dio una buena respuesta que quiero extender, no, reducir … solo a python 3.

except Exception as e:
    e.args = ("Some failure state", *e.args)
    raise

Lo único nuevo es el expansión/desempaquetado de parámetros lo que lo hace lo suficientemente pequeño y fácil de usar para mí.

Intentalo:

foo = None

try:
    try:
        state = "bar"
        foo.append(state)

    except Exception as e:
        e.args = ("Appending '"+state+"' failed", *e.args)
        raise

    print(foo[0]) # would raise too

except Exception as e:
    e.args = ("print(foo) failed: " + str(foo), *e.args)
    raise

Esto te dará:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")

Una simple impresión bonita podría ser algo como

print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))

avatar de usuario
Kee

Un enfoque útil que utilicé es usar el atributo de clase como almacenamiento para los detalles, ya que se puede acceder al atributo de clase tanto desde el objeto de clase como desde la instancia de clase:

class CustomError(Exception):
    def __init__(self, details: Dict):
        self.details = details

Luego en tu código:

raise CustomError({'data': 5})

Y al detectar un error:

except CustomError as e:
    # Do whatever you want with the exception instance
    print(e.details)

  • No es realmente útil ya que el OP solicita que los detalles se impriman como parte del seguimiento de la pila cuando se lanza la excepción original y no se detecta.

    – cowbert

    31 de julio de 2017 a las 14:24

  • Creo que la solución es buena. Pero la descripción no es cierta. Los atributos de clase se copian en las instancias cuando las crea. Entonces, cuando modifique el atributo “detalles” de la instancia, el atributo de clase seguirá siendo Ninguno. De todos modos, queremos este comportamiento aquí.

    – Adán Wallner

    12/09/2019 a las 21:20

avatar de usuario
Pedro M Duarte

Proporcionaré un fragmento de código que uso a menudo cada vez que quiero agregar información adicional a una excepción. Trabajo tanto en Python 2.7 como en 3.6.

import sys
import traceback

try:
    a = 1
    b = 1j

    # The line below raises an exception because
    # we cannot compare int to complex.
    m = max(a, b)  

except Exception as ex:
    # I create my  informational message for debugging:
    msg = "a=%r, b=%r" % (a, b)

    # Gather the information from the original exception:
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Format the original exception for a nice printout:
    traceback_string = ''.join(traceback.format_exception(
        exc_type, exc_value, exc_traceback))

    # Re-raise a new exception of the same class as the original one, 
    # using my custom message and the original traceback:
    raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))

El código anterior da como resultado el siguiente resultado:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
     14     raise type(ex)(
     15         "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16         (msg, traceback_string))

TypeError: a=1, b=1j

ORIGINAL TRACEBACK:

Traceback (most recent call last):
  File "<ipython-input-6-09b74752c60d>", line 7, in <module>
    m = max(a, b)  # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers

Sé que esto se desvía un poco del ejemplo proporcionado en la pregunta, pero, sin embargo, espero que alguien lo encuentre útil.

  • No es realmente útil ya que el OP solicita que los detalles se impriman como parte del seguimiento de la pila cuando se lanza la excepción original y no se detecta.

    – cowbert

    31 de julio de 2017 a las 14:24

  • Creo que la solución es buena. Pero la descripción no es cierta. Los atributos de clase se copian en las instancias cuando las crea. Entonces, cuando modifique el atributo “detalles” de la instancia, el atributo de clase seguirá siendo Ninguno. De todos modos, queremos este comportamiento aquí.

    – Adán Wallner

    12/09/2019 a las 21:20

avatar de usuario
bukzor

A diferencia de las respuestas anteriores, esto funciona frente a excepciones con muy malas __str__. Eso lo hace Sin embargo, modifique el tipo para eliminar los datos inútiles. __str__ implementaciones.

Todavía me gustaría encontrar una mejora adicional que no modifique el tipo.

from contextlib import contextmanager
@contextmanager
def helpful_info():
    try:
        yield
    except Exception as e:
        class CloneException(Exception): pass
        CloneException.__name__ = type(e).__name__
        CloneException.__module___ = type(e).__module__
        helpful_message="%s\n\nhelpful info!" % e
        import sys
        raise CloneException, helpful_message, sys.exc_traceback


class BadException(Exception):
    def __str__(self):
        return 'wat.'

with helpful_info():
    raise BadException('fooooo')

Se conservan el rastreo original y el tipo (nombre).

Traceback (most recent call last):
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
  File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
    self.gen.throw(type, value, traceback)
  File "re_raise.py", line 5, in helpful_info
    yield
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
__main__.BadException: wat.

helpful info!

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad