try print pass exceptions example error custom catch and all python exception-handling try-catch-finally finally

print - ¿''Finalmente'' siempre se ejecuta en Python?



try catch python example (6)

Para cualquier posible bloque try-finally en Python, ¿está garantizado que el bloque finally siempre se ejecutará?

Por ejemplo, digamos que regreso mientras estoy en un bloque except :

try: 1/0 except ZeroDivisionError: return finally: print("Does this code run?")

O tal vez vuelvo a plantear una Exception :

try: 1/0 except ZeroDivisionError: raise finally: print("What about this code?")

Las pruebas muestran que finally se ejecuta para los ejemplos anteriores, pero imagino que hay otros escenarios en los que no he pensado.

¿Hay algún escenario en el que un bloque finally no pueda ejecutarse en Python?


"Garantizado" es una palabra mucho más fuerte que cualquier implementación de finally merece. Lo que está garantizado es que si la ejecución fluye fuera de todo el try , finally construir, pasará finally por hacerlo. Lo que no está garantizado es que la ejecución fluirá fuera del try , finally .

  • Un finally en un generador o en una rutina asíncrona podría nunca ejecutarse , si el objeto nunca se ejecuta hasta la conclusión. Hay muchas maneras en que podría suceder; Aquí hay uno:

    def gen(text): try: for line in text: try: yield int(line) except: # Ignore blank lines - but catch too much! pass finally: print(''Doing important cleanup'') text = [''1'', '''', ''2'', '''', ''3''] if any(n > 1 for n in gen(text)): print(''Found a number'') print(''Oops, no cleanup.'')

    Tenga en cuenta que este ejemplo es un poco complicado: cuando el generador se recolecta basura, Python intenta ejecutar el bloque finally lanzando una excepción GeneratorExit , pero aquí capturamos esa excepción y luego yield nuevamente, momento en el que Python imprime una advertencia (" generador ignorado GeneratorExit ") y se da por vencido. Ver PEP 342 (Corutinas a través de generadores mejorados) para más detalles.

    Otras formas en que un generador o una rutina no pueden ejecutarse hasta la conclusión incluyen si el objeto nunca es GC''ed (sí, eso es posible, incluso en CPython), o si un async with await en __aexit__ , o si el objeto await o yield s en un bloque finally . Esta lista no pretende ser exhaustiva.

  • finally un subproceso de daemon nunca se ejecutará si todos los subprocesos que no son daemon salen primero.

  • os._exit detendrá el proceso inmediatamente sin ejecutar finally bloques.

  • os.fork puede hacer que finally bloques se ejecuten dos veces . Además de los problemas normales que esperaría de las cosas que suceden dos veces, esto podría causar conflictos de acceso concurrentes (bloqueos, paradas, ...) si el acceso a los recursos compartidos no se sincroniza correctamente .

    Dado que el multiprocessing utiliza fork-without-exec para crear procesos de trabajo cuando se usa el método de inicio de fork (el valor predeterminado en Unix), y luego llama a os._exit en el trabajador una vez que se realiza el trabajo del trabajador, finally y la interacción de multiprocessing puede ser problemática ( example )

  • Una falla de segmentación de nivel C evitará que finally se ejecuten los bloques.
  • kill -SIGKILL evitará que finally se ejecuten bloques. SIGTERM y SIGHUP también evitarán que finally se ejecuten los bloques a menos que instale un controlador para controlar el apagado usted mismo; de forma predeterminada, Python no maneja SIGTERM o SIGHUP .
  • Una excepción en finally puede evitar que se complete la limpieza. Un caso particularmente notable es si el usuario toca control-C justo cuando estamos comenzando a ejecutar el bloque finally . Python generará un KeyboardInterrupt y omitirá cada línea del contenido del bloque finally . (El código seguro KeyboardInterrupt es muy difícil de escribir).
  • Si la computadora pierde energía, o si hiberna y no se despierta, finally bloques no se ejecutarán.

El bloque finally no es un sistema de transacción; no proporciona garantías de atomicidad ni nada por el estilo. Algunos de estos ejemplos pueden parecer obvios, pero es fácil olvidar que tales cosas pueden suceder y confiar finally en demasiado.


De acuerdo con la documentación de Python :

No importa lo que sucedió anteriormente, el bloque final se ejecuta una vez que se completa el bloque de código y se manejan las excepciones planteadas. Incluso si hay un error en un controlador de excepciones o en el bloque else y se genera una nueva excepción, el código en el bloque final todavía se ejecuta.

También debe tenerse en cuenta que si hay varias declaraciones de retorno, incluida una en el bloque de finalmente, entonces el retorno de último bloque es el único que se ejecutará.


Encontré este sin usar una función de generador:

import multiprocessing import time def fun(arg): try: print("tried " + str(arg)) time.sleep(arg) finally: print("finally cleaned up " + str(arg)) return foo list = [1, 2, 3] multiprocessing.Pool().map(fun, list)

La suspensión puede ser cualquier código que pueda ejecutarse durante períodos de tiempo inconsistentes.

Lo que parece estar sucediendo aquí es que el primer proceso paralelo que termina deja el bloque try con éxito, pero luego intenta devolver de la función un valor (foo) que no se ha definido en ninguna parte, lo que causa una excepción. Esa excepción mata el mapa sin permitir que los otros procesos alcancen finalmente sus bloques.

Además, si agrega la línea bar = bazz justo después de la llamada sleep () en el bloque try. Luego, el primer proceso para llegar a esa línea arroja una excepción (porque el bazz no está definido), lo que hace que se ejecute su propio bloque finalmente, pero luego mata el mapa, haciendo que los otros bloques de prueba desaparezcan sin llegar a sus bloques finalmente, y el primer proceso para no llegar a su declaración de retorno, tampoco.

Lo que esto significa para el multiprocesamiento de Python es que no puede confiar en el mecanismo de manejo de excepciones para limpiar recursos en todos los procesos si incluso uno de los procesos puede tener una excepción. Sería necesario un manejo adicional de la señal o la administración de los recursos fuera de la llamada del mapa de multiprocesamiento.


Para comprender realmente cómo funciona, simplemente ejecute estos dos ejemplos:

  • try: 1 except: print ''except'' finally: print ''finally''

    saldrá

    finalmente

  • try: 1/0 except: print ''except'' finally: print ''finally''

    saldrá

    excepto
    finalmente


Pues sí y no.

Lo que está garantizado es que Python siempre intentará ejecutar el bloque finalmente. En el caso de que regrese del bloque o genere una excepción no capturada, el bloque finalmente se ejecuta justo antes de regresar o aumentar la excepción.

(lo que podría haber controlado usted mismo simplemente ejecutando el código en su pregunta)

El único caso que puedo imaginar donde el bloque finalmente no se ejecutará es cuando el propio intérprete de Python se bloquea, por ejemplo, dentro del código C o debido a un corte de energía.


Sí. Finalmente siempre gana.

La única forma de derrotarlo es detener la ejecución antes de que finally: tenga la oportunidad de ejecutarse (por ejemplo, bloquear el intérprete, apagar la computadora, suspender un generador para siempre).

Me imagino que hay otros escenarios en los que no he pensado.

Aquí hay un par más que quizás no haya pensado:

def foo(): # finally always wins try: return 1 finally: return 2 def bar(): # even if he has to eat an unhandled exception, finally wins try: raise Exception(''boom'') finally: return ''no boom''

Dependiendo de cómo salga del intérprete, a veces puede "cancelar" finalmente, pero no así:

>>> import sys >>> try: ... sys.exit() ... finally: ... print(''finally wins!'') ... finally wins! $

Usando el precario os._exit (en mi opinión, esto se os._exit "bloqueo del intérprete"):

>>> import os >>> try: ... os._exit(1) ... finally: ... print(''finally!'') ... $

Actualmente estoy ejecutando este código, para probar si finalmente todavía se ejecutará después de la muerte por calor del universo:

try: while True: sleep(1) finally: print(''done'')

Sin embargo, todavía estoy esperando el resultado, así que vuelve aquí más tarde.