python - try - ¿Cómo determinar si se generó una excepción una vez que estás en el bloque finally?
try except anidados python (5)
Usando un gestor de contexto
Puede usar un gestor de contexto personalizado, por ejemplo:
class DidWeRaise:
__slots__ = (''exception_happened'', ) # instances will take less memory
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# If no exception happened the `exc_type` is None
self.exception_happened = exc_type is not None
Y luego usa eso dentro del try
:
try:
with DidWeRaise() as error_state:
# funky code
finally:
if error_state.exception_happened:
print(''the funky code raised'')
Sigue siendo una variable adicional, pero probablemente sea mucho más fácil de reutilizar si desea usarlo en varios lugares. Y no necesita alternarlo usted mismo.
Usando una variable
En caso de que no desee el gestor de contexto, invertiría la lógica del desencadenador y lo cambiaría solo en caso de que no haya ocurrido ninguna excepción. De esta forma, no necesita un caso except
para excepciones que no desea manejar. El lugar más apropiado sería la cláusula else
que se ingresa en caso de que el try
no arrojara una excepción:
exception_happened = True
try:
# funky code
except HandleThis:
# handle this kind of exception
else:
exception_happened = False
finally:
if exception_happened:
print(''the funky code raised'')
Y como ya se señaló en lugar de tener una variable "alternar", podría reemplazarla (en este caso) con la función de registro deseada:
mylog = mylogger.WARNING
try:
with LogCapture() as log:
funky_code()
except HandleThis:
# handle this kind of exception
else:
# In case absolutely no exception was thrown in the try we can log on debug level
mylog = mylogger.DEBUG
finally:
for record in log.captured:
mylog(record.msg, record.args)
Por supuesto, también funcionaría si lo colocas al final de tu try
(como sugieren otras respuestas aquí), pero prefiero la cláusula else
porque tiene más significado ("ese código debe ejecutarse solo si no hubo excepción en el bloque try
") y puede ser más fácil de mantener a largo plazo. Aunque todavía es más necesario mantener que el administrador de contexto porque la variable se establece y se alterna en diferentes lugares.
Uso de sys.exc_info
(funciona solo para excepciones no controladas)
El último enfoque que quiero mencionar probablemente no sea útil para usted, pero tal vez sea útil para futuros lectores que solo quieran saber si hay una excepción no controlada (una excepción que no se detectó en ningún bloque except
o se ha generado dentro de un bloque except
). En ese caso, puede usar sys.exc_info
:
import sys
try:
# funky code
except HandleThis:
pass
finally:
if sys.exc_info()[0] is not None:
# only entered if there''s an *unhandled* exception, e.g. NOT a HandleThis exception
print(''funky code raised'')
¿Es posible decir si hubo una excepción una vez que está en la cláusula finally
? Algo como:
try:
funky code
finally:
if ???:
print(''the funky code raised'')
Estoy buscando hacer algo como esto más SECO:
try:
funky code
except HandleThis:
# handle it
raised = True
except DontHandleThis:
raised = True
raise
else:
raised = False
finally:
logger.info(''funky code raised %s'', raised)
No me gusta que requiera capturar una excepción, que no tiene la intención de manejar, solo para establecer una bandera.
Como algunos comments piden menos "M" en el MCVE, aquí hay más antecedentes sobre el caso de uso. El problema real es acerca de la escalada de niveles de registro.
- El código funky es de terceros y no se puede cambiar.
- La excepción de falla y el seguimiento de la pila no contienen información de diagnóstico útil, por lo que usar
logger.exception
en un bloque except no es útil aquí. - Si se generó el código funky, entonces cierta información que necesito ver ya se ha registrado, en el nivel DEBUG. No podemos y no podemos manejar el error, pero queremos escalar el registro de DEPURACIÓN porque la información necesaria está ahí.
- El código funky no aumenta, la mayoría de las veces. No quiero escalar los niveles de registro para el caso general, porque es demasiado detallado.
Por lo tanto, el código se ejecuta en un contexto de captura de registro (que configura los controladores personalizados para interceptar registros) y cierta información de depuración se vuelve a registrar retrospectivamente:
try:
with LogCapture() as log:
funky_code() # <-- third party badness
finally:
mylog = mylogger.WARNING if <there was exception> else mylogger.DEBUG
for record in log.captured:
mylog(record.msg, record.args)
Bien, entonces, ¿qué significa que realmente solo quieres modificar tu administrador de contexto existente o utilizar un enfoque similar? logbook
realidad tiene algo llamado FingersCrossedHandler
que haría exactamente lo que quieres. Pero podrías hacerlo tú mismo, como:
@contextmanager
def LogCapture():
# your existing buffer code here
level = logging.WARN
try:
yield
except UselessException:
level = logging.DEBUG
raise # Or don''t, if you just want it to go away
finally:
# emit logs here
Respuesta original
Estás pensando en esto un poco de lado.
Tiene la intención de manejar la excepción; lo está gestionando configurando un indicador. Tal vez no te importa nada más (lo que parece una mala idea), pero si te importa hacer algo cuando se produce una excepción, entonces debes ser explícito al respecto.
El hecho de que esté configurando una variable, pero desea que la excepción continúe significa que lo que realmente desea es plantear su propia excepción específica, a partir de la excepción que se planteó:
class MyPkgException(Exception): pass
class MyError(PyPkgException): pass # If there''s another exception type, you can also inherit from that
def do_the_badness():
try:
raise FileNotFoundError(''Or some other code that raises an error'')
except FileNotFoundError as e:
raise MyError(''File was not found, doh!'') from e
finally:
do_some_cleanup()
try:
do_the_badness()
except MyError as e:
print(''The error? Yeah, it happened'')
Esto resuelve:
- Manejando explícitamente la (s) excepción (es) que está tratando de manejar
- Hacer que los restos de la pila y las excepciones originales estén disponibles
- Permitir que su código maneje la excepción original en otro lugar para manejar su excepción lanzada
- Permitir que un código de manejo de excepciones de primer nivel simplemente capture
MyPkgException
para capturar todas sus excepciones, de modo que pueda registrar algo y salir con un estado agradable en lugar de un rastro feo de la pila
Puede asignar fácilmente su excepción atrapada a una variable y usarla en el bloque finally, por ejemplo:
>>> x = 1
>>> error = None
>>> try:
... x.foo()
... except Exception as e:
... error = e
... finally:
... if error is not None:
... print(error)
...
''int'' object has no attribute ''foo''
Si fuera yo, haría una pequeña reordenación de tu código.
raised = False
try:
# funky code
except HandleThis:
# handle it
raised = True
except Exception as ex:
# Don''t Handle This
raise ex
finally:
if raised:
logger.info(''funky code was raised'')
He colocado la asignación booleana planteada fuera de la declaración de prueba para garantizar el alcance y he convertido la sentencia de excepción final en un controlador de excepción general para las excepciones que no desea manejar.
Este estilo determina si tu código falló. Otro enfoque me puede ayudar a determinar cuándo tu código tiene éxito.
success = False
try:
# funky code
success = True
except HandleThis:
# handle it
pass
except Exception as ex:
# Don''t Handle This
raise ex
finally:
if success:
logger.info(''funky code was successful'')
else:
logger.info(''funky code was raised'')
raised = True
try:
funky code
raised = False
except HandleThis:
# handle it
finally:
logger.info(''funky code raised %s'', raised)
Dada la información de antecedentes adicional agregada a la pregunta sobre la selección de un nivel de registro, esto parece muy fácil de adaptar al caso de uso previsto:
mylog = WARNING
try:
funky code
mylog = DEBUG
except HandleThis:
# handle it
finally:
mylog(...)