Pour tout bloc try-finally possible en Python, est-il garanti que le bloc finally
sera toujours exécuté?
Par exemple, disons que je retourne dans un bloc except
:
try: 1/0 except ZeroDivisionError: return finally: print("Does this code run?")
Ou peut-être que je relance une Exception
:
try: 1/0 except ZeroDivisionError: raise finally: print("What about this code?")
Les tests montrent que finally
il est exécuté pour les exemples ci-dessus, mais j’imagine qu’il y a d’autres scénarios auxquels je n’ai pas pensé.
Existe-t-il des scénarios dans lesquels un bloc finally
peut ne pas s’exécuter en Python?
“Garanti” est un mot beaucoup plus fort que ce que mérite finally
toute mise en œuvre. Ce qui est garanti, c’est que si l’exécution sort de l’ try
, la construction finira par passer. Ce qui n’est pas garanti, c’est que l’exécution va sortir de l’ try
– finally
.
Une coroutine finale dans un générateur ou asynchrone pourrait ne jamais s’exécuter , si l’object ne s’exécute jamais jusqu’à la conclusion. Il y a beaucoup de façons qui pourraient arriver; en voici un:
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.')
Notez que cet exemple est un peu délicat: lorsque le générateur est nettoyé, Python tente d’exécuter le bloc finally
en lançant une exception GeneratorExit
, mais nous interceptons cette exception et yield
nouveau, et Python imprime un avertissement (” générateur ignoré GeneratorExit “) et abandonne. Voir PEP 342 (Coroutines via Enhanced Generators) pour plus de détails.
Parmi les autres façons dont un générateur ou une coroutine pourrait ne pas exécuter jusqu’à la fin, notons si l’object n’est jamais GC’ed (oui, c’est possible, même en CPython), ou si un async with
await
dans __aexit__
ou si l’object await
s dans un bloc finally
. Cette liste n’est pas exhaustive.
Un thread finally
dans un démon peut ne jamais s’exécuter si tous les threads non-démons sortent en premier.
os._exit
arrêtera immédiatement le processus sans exécuter finally
blocs.
os.fork
peut finally
exécuter deux fois les blocs. En plus des problèmes normaux auxquels vous vous attendez à deux resockets, cela pourrait entraîner des conflits d’access simultanés (pannes, blocages, etc.) si l’access aux ressources partagées n’est pas correctement synchronisé .
Étant donné que le multiprocessing
utilise fork-without-exec pour créer des processus de travail lors de l’utilisation de la méthode fork start (par défaut sous Unix), puis appelle os._exit
dans worker une fois le travail terminé, l’interaction multiprocessing
peut poser problème ).
kill -SIGKILL
empêchera finally
blocs de s’exécuter. SIGTERM
et SIGHUP
empêchent également les blocs d’exécuter, à moins que vous installiez un gestionnaire pour contrôler vous-même l’arrêt; Par défaut, Python ne gère pas SIGTERM
ou SIGHUP
. finally
empêcher le nettoyage de se terminer. Un cas particulièrement remarquable est que l’utilisateur frappe control-C au moment où nous commençons à exécuter le bloc finally
. Python déclenchera une KeyboardInterrupt
et ignorera chaque ligne du contenu du bloc finally
. ( KeyboardInterrupt
code KeyboardInterrupt
-safe est très difficile à écrire). Le bloc finally
n’est pas un système de transaction; il ne fournit pas de garanties d’atomicité ni rien du genre. Certains de ces exemples peuvent sembler évidents, mais il est facile d’oublier que de telles choses peuvent se produire et qu’on en a finally
pour trop.
Oui. Enfin, presque toujours gagne.
La principale façon de le vaincre est d’interrompre l’exécution avant d’être finally:
a une chance d’exécuter (par exemple, crasher l’interpréteur, éteindre votre ordinateur, suspendre un générateur pour toujours).
J’imagine qu’il y a d’autres scénarios auxquels je n’ai pas pensé.
Voici quelques exemples auxquels vous n’avez peut-être pas pensé:
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'
Selon la façon dont vous quittez l’interpréteur, vous pouvez parfois “annuler” finalement, mais pas comme ceci:
>>> import sys >>> try: ... sys.exit() ... finally: ... print('finally wins!') ... finally wins! $
En utilisant le précaire os._exit
(cela relève de “crash the interpreter” à mon avis):
>>> import os >>> try: ... os._exit(1) ... finally: ... print('finally!') ... $
Je suis actuellement en cours d’exécution de ce code, pour tester si enfin sera encore exécuté après la chaleur mort de l’univers:
try: while True: sleep(1) finally: print('done')
Cependant, j’attends toujours le résultat, alors revenez ici plus tard.
Selon la documentation Python :
Peu importe ce qui s’est passé précédemment, le bloc final est exécuté une fois le bloc de code terminé et toutes les exceptions levées gérées. Même s’il y a une erreur dans un gestionnaire d’exceptions ou le bloc else et qu’une nouvelle exception est déclenchée, le code du bloc final est toujours exécuté.
Il convient également de noter que s’il y a plusieurs instructions de retour, y compris une dans le bloc finally, alors le retour de bloc final est le seul à s’exécuter.
Eh bien, oui et non.
Ce qui est garanti, c’est que Python essaiera toujours d’exécuter le bloc finally. Dans le cas où vous revenez du bloc ou déclenchez une exception non interceptée, le bloc finally est exécuté juste avant de renvoyer ou de déclencher l’exception.
(ce que vous auriez pu contrôler vous-même en exécutant simplement le code dans votre question)
Le seul cas où je peux imaginer où le bloc finally ne sera pas exécuté, c’est lorsque l’interpréteur Python se plante lui-même, par exemple dans le code C ou à cause d’une panne de courant.