¿”Excepción interna” (con rastreo) en Python?

Mi fondo está en C # y recientemente comencé a progtwigr en Python. Cuando se lanza una excepción, normalmente quiero envolverla en otra excepción que agregue más información, mientras muestro el seguimiento completo de la stack. Es bastante fácil en C #, pero ¿cómo lo hago en Python?

P.ej. en C # haría algo como esto:

try { ProcessFile(filePath); } catch (Exception ex) { throw new ApplicationException("Failed to process file " + filePath, ex); } 

En Python puedo hacer algo similar:

 try: ProcessFile(filePath) except Exception as e: raise Exception('Failed to process file ' + filePath, e) 

… pero esto pierde el rastro de la excepción interna!

Edición: me gustaría ver los mensajes de excepción y ambos seguimientos de stack y correlacionar los dos. Es decir, quiero ver en la salida que la excepción X ocurrió aquí y luego la excepción Y allí, igual que lo haría en C #. ¿Es esto posible en Python 2.6? Parece que lo mejor que puedo hacer hasta ahora (según la respuesta de Glenn Maynard) es:

 try: ProcessFile(filePath) except Exception as e: raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2] 

Esto incluye tanto los mensajes como las respuestas de rastreo, pero no muestra qué excepción ocurrió en el rastreo.

Python 2

Es sencillo; pasar el rastreo como el tercer argumento para plantear.

 import sys class MyException(Exception): pass try: raise TypeError("test") except TypeError, e: raise MyException(), None, sys.exc_info()[2] 

Siempre haga esto cuando atrape una excepción y vuelva a subir otra.

Python 3

En Python 3 puedes hacer lo siguiente:

 try: raise MyExceptionToBeWrapped("I have twisted my ankle") except MyExceptionToBeWrapped as e: raise MyWrapperException("I'm not in a good shape") from e 

Esto producirá algo como esto:

  Traceback (most recent call last): ... MyExceptionToBeWrapped: ("I have twisted my ankle") The above exception was the direct cause of the following exception: Traceback (most recent call last): ... MyWrapperException: ("I'm not in a good shape") 

Python 3 tiene el raisefrom cláusula a las excepciones de la cadena. La respuesta de Glenn es excelente para Python 2.7, pero solo usa el rastreo de la excepción original y elimina el mensaje de error y otros detalles. Aquí hay algunos ejemplos en Python 2.7 que agregan información de contexto del scope actual al mensaje de error de la excepción original, pero mantienen intactos otros detalles.

Tipo de excepción conocida

 try: sock_common = xmlrpclib.ServerProxy(rpc_url+'/common') self.user_id = sock_common.login(self.dbname, username, self.pwd) except IOError: _, ex, traceback = sys.exc_info() message = "Connecting to '%s': %s." % (config['connection'], ex.strerror) raise IOError, (ex.errno, message), traceback 

Ese sabor de la instrucción raise toma el tipo de excepción como la primera expresión, los argumentos del constructor de la clase de excepción en una tupla como la segunda expresión y el rastreo como la tercera expresión. Si está ejecutando una sys.exc_info() anterior a Python 2.2, consulte las advertencias en sys.exc_info() .

Cualquier tipo de excepción

Este es otro ejemplo que tiene un propósito más general si no sabe qué tipo de excepciones podría tener que detectar su código. El inconveniente es que pierde el tipo de excepción y solo genera un RuntimeError. Tienes que importar el módulo de traceback .

 except Exception: extype, ex, tb = sys.exc_info() formatted = traceback.format_exception_only(extype, ex)[-1] message = "Importing row %d, %s" % (rownum, formatted) raise RuntimeError, message, tb 

Modificar el mensaje

Aquí hay otra opción si el tipo de excepción le permitirá agregarle contexto. Puede modificar el mensaje de la excepción y luego volver a subirlo.

 import subprocess try: final_args = ['lsx', '/home'] s = subprocess.check_output(final_args) except OSError as ex: ex.strerror += ' for command {}'.format(final_args) raise 

Eso genera el siguiente seguimiento de stack:

 Traceback (most recent call last): File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in  s = subprocess.check_output(final_args) File "/usr/lib/python2.7/subprocess.py", line 566, in check_output process = Popen(stdout=PIPE, *popenargs, **kwargs) File "/usr/lib/python2.7/subprocess.py", line 710, in __init__ errread, errwrite) File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child raise child_exception OSError: [Errno 2] No such file or directory for command ['lsx', '/home'] 

Puede ver que muestra la línea donde se llamó a check_output() , pero el mensaje de excepción ahora incluye la línea de comando.

En Python 3.x :

 raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__) 

o simplemente

 except Exception: raise MyException() 

que propagará MyException pero imprimirá ambas excepciones si no se manejará.

En Python 2.x :

 raise Exception, 'Failed to process file ' + filePath, e 

Puede evitar la impresión de ambas excepciones __context__ atributo __context__ . Aquí escribo un administrador de contexto que lo utiliza para detectar y cambiar su excepción sobre la marcha: (consulte http://docs.python.org/3.1/library/stdtypes.html para conocer cómo funcionan)

 try: # Wrap the whole program into the block that will kill __context__. class Catcher(Exception): '''This context manager reraises an exception under a different name.''' def __init__(self, name): super().__init__('Failed to process code in {!r}'.format(name)) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: self.__traceback__ = exc_tb raise self ... with Catcher('class definition'): class a: def spam(self): # not really pass, but you get the idea pass lut = [1, 3, 17, [12,34], 5, _spam] assert a().lut[-1] == a.spam ... except Catcher as e: e.__context__ = None raise 

No creo que puedas hacer esto en Python 2.x, pero algo similar a esta funcionalidad es parte de Python 3. Desde PEP 3134 :

En la implementación de Python de hoy, las excepciones se componen de tres partes: el tipo, el valor y el rastreo. El módulo ‘sys’, expone la excepción actual en tres variables paralelas, exc_type, exc_value y exc_traceback, la función sys.exc_info () devuelve una tupla de estas tres partes, y la statement ‘raise’ tiene una forma de tres argumentos que acepta estas tres partes Manipular las excepciones a menudo requiere pasar estas tres cosas en paralelo, lo que puede ser tedioso y propenso a errores. Además, la statement ‘excepto’ solo puede proporcionar acceso al valor, no al rastreo. La adición del atributo ‘ traceback ‘ a los valores de excepción hace que toda la información de excepción sea accesible desde un solo lugar.

Comparación con C #:

Las excepciones en C # contienen una propiedad ‘InnerException’ de solo lectura que puede apuntar a otra excepción. Su documentación [10] dice que “Cuando se lanza una excepción X como resultado directo de una excepción anterior Y, la propiedad InnerException de X debe contener una referencia a Y.” Esta propiedad no es establecida automáticamente por la máquina virtual; más bien, todos los constructores de excepciones toman un argumento opcional ‘innerException’ para establecerlo explícitamente. El atributo ‘ causa ‘ cumple el mismo propósito que InnerException, pero este PEP propone una nueva forma de ‘boost’ en lugar de extender los constructores de todas las excepciones. C # también proporciona un método GetBaseException que salta directamente al final de la cadena InnerException; Este PEP no propone ningún análogo.

Tenga en cuenta también que Java, Ruby y Perl 5 tampoco admiten este tipo de cosas. Cotizando de nuevo:

En cuanto a otros lenguajes, Java y Ruby descartan la excepción original cuando se produce otra excepción en una cláusula de “captura” / “rescate” o “finalmente” / “garantía”. Perl 5 carece de manejo de excepciones estructurado incorporado. Para Perl 6, el número de RFC 88 [9] propone un mecanismo de excepción que retiene implícitamente las excepciones encadenadas en una matriz llamada @@.

Podría usar mi clase CausedException para encadenar excepciones en Python 2.x (e incluso en Python 3 puede ser útil en caso de que quiera dar más de una excepción detectada como causa a una excepción recién surgida). Puede que esto te ayude.

Tal vez usted podría tomar la información relevante y pasarla? Estoy pensando algo como:

 import traceback import sys import StringIO class ApplicationError: def __init__(self, value, e): s = StringIO.StringIO() traceback.print_exc(file=s) self.value = (value, s.getvalue()) def __str__(self): return repr(self.value) try: try: a = 1/0 except Exception, e: raise ApplicationError("Failed to process file", e) except Exception, e: print e 

Asumiendo:

  • necesita una solución, que funciona para Python 2 (para Python 3 puro, vea raise ... from solución)
  • solo quiero enriquecer el mensaje de error, por ejemplo, proporcionar algún contexto adicional
  • necesita la traza de stack completa

puede utilizar una solución simple de los documentos https://docs.python.org/3/tutorial/errors.html#raising-exceptions :

 try: raise NameError('HiThere') except NameError: print 'An exception flew by!' # print or log, provide details about context raise # reraise the original exception, keeping full stack trace 

La salida:

 An exception flew by! Traceback (most recent call last): File "", line 2, in ? NameError: HiThere 

Parece que la pieza clave es la palabra clave “boost” simplificada que está sola. Eso volverá a elevar la excepción en el bloque de excepción.

Para una máxima compatibilidad entre Python 2 y 3, puedes usar raise_from en la biblioteca six . https://six.readthedocs.io/#six.raise_from . Aquí está su ejemplo (ligeramente modificado para mayor claridad):

 import six try: ProcessFile(filePath) except Exception as e: six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)