Est-ce que ‘finalement’ s’exécute toujours en Python?

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’ tryfinally .

  • 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 ).

  • Une erreur de segmentation au niveau C empêchera les blocs de fonctionner.
  • 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 .
  • Une exception peut 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).
  • Si l’ordinateur perd de l’alimentation, ou s’il hiberne et ne se réveille pas, les blocs ne fonctionneront plus.

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.