¿Agregando información a una excepción?

EDIT: estoy corriendo python 2.6

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?

Lo haría así, así que cambiar su tipo en foo() no requerirá también cambiarlo en la 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  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 pequeña 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  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 en mi primera actualización es sintácticamente incorrecto más la idea de tener un atributo de message en BaseException se retiró en un cambio a PEP 352 el 2012-05-16 (mi primera actualización se publicó el 2012-03- 12). Por lo tanto, actualmente, en Python 3.5.2 de todos modos, deberías hacer algo en este sentido para conservar el rastreo y no codificar el tipo de excepción en la bar() funciones 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 que se muestran.

 # 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 una manera que funcionara tanto en Python 2 como en 3. Aunque la respuesta podría parecer “No” debido a las diferencias de syntax, hay una forma de evitarlo utilizando una función auxiliar como reraise() en Los six módulos adicionales. Por lo tanto, si prefiere no usar la biblioteca por alguna razón, a continuación se muestra una versión independiente simplificada.

Tenga en cuenta también que, dado que la excepción se vuelve a activar dentro de la función reraise() , 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') 

En caso de que haya venido buscando una solución para Python3, el manual dice:

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

 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 parece a esto 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. 

Convertir un TypeError totalmente anodino en un mensaje agradable con sugerencias hacia una solución sin arruinar la excepción original.

Suponiendo que no quiera o no pueda 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 

Esta es, de hecho, 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 reenvío se agregue a la traza de la stack, escribir raise e lugar de raise hará el truco.

Un método práctico que utilicé es usar el atributo de clase como almacenamiento para 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): details = None 

Luego en tu código:

 exc = CustomError('Some message') exc.details('Details -- add whatever you want') raise exc 

Y al detectar un error:

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

A diferencia de las respuestas anteriores, esto funciona frente a las excepciones con __str__ realmente malo. Sin embargo, modifica el tipo para descartar implementaciones __str__ inútiles.

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

El rastreo original y el tipo (nombre) se conservan.

 Traceback (most recent call last): File "re_raise.py", line 20, in  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  raise BadException('fooooo') __main__.BadException: wat. helpful info! 

Proporcionaré un fragmento de código que uso a menudo cada vez que quiero agregar información adicional a una excepción. Yo 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)  in () 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 "", line 7, in  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.

Puede definir su propia excepción que se hereda de otra y crear su propio constructor para establecer el valor.

Por ejemplo:

 class MyError(Exception): def __init__(self, value): self.value = value Exception.__init__(self) def __str__(self): return repr(self.value) 

Tal vez

 except Exception as e: raise IOError(e.message + 'happens at %s'%arg1)