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ónGeneratorExit
, pero aquí capturamos esa excepción y luegoyield
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 objetoawait
oyield
s en un bloquefinally
. 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 ejecutarfinally
bloques. -
os.fork
puede hacer quefinally
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 aos._exit
en el trabajador una vez que se realiza el trabajo del trabajador,finally
y la interacción demultiprocessing
puede ser problemática ( example ) -
Una falla de segmentación de nivel C evitará que
finally
se ejecuten los bloques. -
kill -SIGKILL
evitará quefinally
se ejecuten bloques.SIGTERM
ySIGHUP
también evitarán quefinally
se ejecuten los bloques a menos que instale un controlador para controlar el apagado usted mismo; de forma predeterminada, Python no manejaSIGTERM
oSIGHUP
. -
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 bloquefinally
. Python generará unKeyboardInterrupt
y omitirá cada línea del contenido del bloquefinally
. (El código seguroKeyboardInterrupt
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.