Generar excepciones cuando ya existe una excepción en Python 3

¿Qué sucede con mi primera excepción ( A ) cuando la segunda ( B ) aparece en el siguiente código?

 class A(Exception): pass class B(Exception): pass try: try: raise A('first') finally: raise B('second') except X as c: print(c) 

Si se ejecuta con X = A obtengo:

  Rastreo (llamadas recientes más última):
   Archivo "raise_more_exceptions.py", línea 6, en 
     elevar A ('primero')
 __main___ A: primero

 Durante el manejo de la excepción anterior, ocurrió otra excepción:

 Rastreo (llamadas recientes más última):
   Archivo "raise_more_exceptions.py", línea 8, en 
     elevar B ('segundo')
 __main __. B: segundo 

Pero si X = B obtengo:

  segundo 

Preguntas

  1. ¿A dónde fue mi primera excepción?
  2. ¿Por qué solo se puede capturar la excepción más externa?
  3. ¿Cómo puedo quitar la excepción más externa y volver a subir las excepciones anteriores?

Actualización0

Esta pregunta aborda específicamente Python 3, ya que su manejo de excepciones es bastante diferente al de Python 2.

Respondiendo a la pregunta 3, puedes usar:

 raise B('second') from None 

Lo que eliminará la excepción A traceback.

 Traceback (most recent call last): File "raising_more_exceptions.py", line 8, in raise B('second') __main__.B: second 

El manejo de excepciones de Pythons solo tratará con una excepción a la vez. Sin embargo, los objetos de excepción están sujetos a las mismas reglas variables y recolección de basura que todo lo demás. Por lo tanto, si guarda el objeto de excepción en una variable en algún lugar, puede manejarlo más tarde, incluso si se produce otra excepción.

En su caso, cuando se genera una excepción durante la statement “finalmente”, Python 3 imprimirá el rastreo de la primera excepción antes de la de la segunda excepción, para que sea más útil.

Un caso más común es que desea generar una excepción durante un manejo de excepciones explícito. Luego puede “guardar” la excepción en la siguiente excepción. Solo pásalo como parámetro:

 >>> class A(Exception): ... pass ... >>> class B(Exception): ... pass ... >>> try: ... try: ... raise A('first') ... except A as e: ... raise B('second', e) ... except Exception as c: ... print(c.args[1]) ... first 

Como ve, ahora puede acceder a la excepción original.

La excepción ‘causante’ está disponible como c .__ context__ en su último controlador de excepciones. Python está utilizando esta información para hacer un seguimiento más útil. Bajo Python 2.x, la excepción original se habría perdido, esto es solo para Python 3.

Por lo general, usaría esto para lanzar una excepción consistente mientras mantiene la excepción original accesible (aunque es bastante bueno que ocurra automáticamente desde un controlador de excepciones, ¡no lo sabía!):

 try: do_something_involving_http() except (URLError, socket.timeout) as ex: raise MyError('Network error') from ex 

Más información (y algunas otras cosas muy útiles que puedes hacer) aquí: http://docs.python.org/3.3/library/exceptions.html

Creo que todos los ingredientes para responder a sus preguntas ya están en las respuestas existentes. Déjame combinar y elaborar.

Permítame repetir el código de su pregunta para proporcionar referencias de números de línea:

  1 class A(Exception): pass 2 class B(Exception): pass 3 4 try: 5 try: 6 raise A('first') 7 finally: 8 raise B('second') 9 except X as c: 10 print(c) 

Así que para responder a sus preguntas:

  1. ¿A dónde fue mi primera excepción?

Su primera excepción A aparece en la línea 6. La cláusula finally en la línea 7 siempre se ejecuta tan pronto como se deja el bloque try (líneas 5-6), independientemente de si se deja debido a que se completó con éxito o debido a una excepción elevada. Mientras se ejecuta la cláusula finally , la línea 8 genera otra excepción B Como lo han señalado Lennart e Ignazio, solo se puede hacer un seguimiento de una excepción, la que se planteó más recientemente. Entonces, tan pronto como se levanta B , el bloque try general (líneas 4-8) se cierra y la excepción B está siendo capturada por la instrucción de except en la línea 9 si coincide (si X es B ).

  1. ¿Por qué solo se puede capturar la excepción más externa?

Esperemos que esto quede claro ahora a partir de mi explicación de 1. Sin embargo, podría detectar la excepción interna / inferior / primera. Para unirte a la respuesta de Lennart, ligeramente modificada, aquí tienes cómo atrapar a ambos:

 class A(Exception): pass class B(Exception): pass try: try: raise A('first') except A as e: raise B('second', e) except Exception as c: print(c) 

La salida es:

 ('second', A('first',)) 
  1. ¿Cómo puedo quitar la excepción más externa y volver a subir las excepciones anteriores?

En el ejemplo de Lennart, la solución a esta pregunta es la línea, except A as e donde la excepción interna / inferior / primera se captura y almacena en la variable e .

Como una sensación general de cuándo atrapar las excepciones, cuándo ignorarlas y cuándo volver a plantearlas, quizás esta pregunta y la respuesta de Alex Martelli ayuden.

  1. Fue echado fuera.
  2. Solo una excepción puede estar “activa” a la vez por subproceso.
  3. No puede, a menos que encapsule la excepción anterior en la excepción posterior de alguna manera.