Pour tout bloc try-finally possible en Python, est-il garanti que le finally
bloc sera toujours exécuté?
Par exemple, disons que je reviens dans un except
bloc:
try:
1/0
except ZeroDivisionError:
return
finally:
print("Does this code run?")
Ou peut-être que je relance un Exception
:
try:
1/0
except ZeroDivisionError:
raise
finally:
print("What about this code?")
Les tests montrent que finally
cela est exécuté pour les exemples ci-dessus, mais j'imagine qu'il existe d'autres scénarios auxquels je n'ai pas pensé.
Existe-t-il des scénarios dans lesquels un finally
bloc peut échouer à s'exécuter en Python?
python
exception-handling
try-catch-finally
finally
Stevoisiak
la source
la source
finally
échouer à exécuter ou "vaincre son objectif" est pendant une boucle infinie,sys.exit
ou une interruption forcée. La documentation indique que celafinally
est toujours exécuté, alors j'irais avec ça.finally
il ne fonctionnera pas. Ou la même chose si l'ordinateur plante avant: Dfinally
ne s'exécutera pas si le cordon d'alimentation est arraché du mur.Réponses:
«Garanti» est un mot beaucoup plus fort que toute implémentation de
finally
mérite. Ce qui est garanti que si l' exécution des flux de l'ensembletry
-finally
construction, il passera par lefinally
faire. Ce qui n'est pas garanti, c'est que l'exécution découlera dutry
-finally
.Un
finally
dans un générateur ou une coroutine asynchrone peut ne jamais s'exécuter , si l'objet ne s'exécute jamais jusqu'à la conclusion. Il y a de nombreuses façons qui pourraient arriver; en voici une:Notez que cet exemple est un peu délicat: lorsque le générateur est ramassé, Python tente d'exécuter le
finally
bloc en lançant uneGeneratorExit
exception, mais ici nous interceptons cette exception puis àyield
nouveau, à quel point Python imprime un avertissement ("générateur ignoré GeneratorExit ") et abandonne. Voir PEP 342 (Coroutines via des générateurs améliorés) pour plus de détails.D'autres façons dont un générateur ou une coroutine peuvent ne pas s'exécuter jusqu'à la conclusion incluent si l'objet n'est tout simplement jamais GC (oui, c'est possible, même en CPython), ou si un
async with
await
s in__aexit__
, ou si l'objetawait
s ouyield
s dans unfinally
bloc. Cette liste ne prétend pas être exhaustive.Un
finally
dans un thread démon peut ne jamais s'exécuter si tous les threads non démon se terminent en premier.os._exit
arrêtera le processus immédiatement sans exécuter definally
blocs.os.fork
peut provoquer l' exécution desfinally
blocs deux fois . En plus des problèmes normaux auxquels vous vous attendez lorsque les choses se passent deux fois, cela pourrait provoquer des conflits d'accès simultanés (plantages, blocages, ...) si l'accès aux ressources partagées n'est pas correctement synchronisé .Depuis
multiprocessing
utilise fork-without-exec pour créer des processus de travail lors de l'utilisation de la méthode de démarrage de fork (par défaut sous Unix), puis appelleos._exit
le worker une fois que le travail du travailleur est terminé,finally
et l'multiprocessing
interaction peut être problématique ( exemple ).finally
blocs de s'exécuter.kill -SIGKILL
empêchera lesfinally
blocs de fonctionner.SIGTERM
etSIGHUP
empêchera également lesfinally
blocs de s'exécuter sauf si vous installez un gestionnaire pour contrôler vous-même l'arrêt; par défaut, Python ne gère pasSIGTERM
ouSIGHUP
.finally
peut empêcher le nettoyage de se terminer. Un cas particulièrement remarquable est si l'utilisateur frappe control-C juste au moment où nous commençons à exécuter lefinally
bloc. Python lèvera aKeyboardInterrupt
et sautera chaque ligne dufinally
contenu du bloc. (KeyboardInterrupt
-safe code est très difficile à écrire).finally
blocs ne fonctionneront pas.Le
finally
bloc n'est pas un système de transaction; il ne fournit aucune garantie d'atomicité ou quoi que ce soit de ce genre. Certains de ces exemples peuvent sembler évidents, mais il est facile d'oublier que de telles choses peuvent arriver etfinally
de trop se fier .la source
except
, et ne jamais attraper à l'GeneratorExit
intérieur d'un générateur. Les points sur les threads / tuer le processus / segfaulting / power off sont attendus, python ne peut pas faire de magie. Aussi: les exceptions dansfinally
sont évidemment un problème mais cela ne change pas le fait que le flux de contrôle a été déplacé vers lefinally
bloc. En ce qui concerneCtrl+C
, vous pouvez ajouter un gestionnaire de signal qui l'ignore, ou simplement "planifier" un arrêt propre une fois l'opération en cours terminée.kill -9
n'a pas spécifié de langue. Et franchement, il faut le répéter, car il se trouve dans un angle mort. Trop de gens oublient, ou ne se rendent pas compte, que leur programme pourrait être stoppé net sans même être autorisé à nettoyer.finally
blocs comme s'ils offraient des garanties transactionnelles. Cela peut sembler évident que non, mais ce n'est pas quelque chose que tout le monde réalise. En ce qui concerne le cas du générateur, il y a beaucoup de façons un générateur pourrait ne pas être GC'ed du tout, et beaucoup de façons un générateur ou coroutine peut accidentellement donner , aprèsGeneratorExit
même si elle ne se coince pasGeneratorExit
, par exemple , si unasync with
suspend une coroutine en__exit__
.Oui. Enfin gagne toujours.
La seule façon de le vaincre est d'arrêter l'exécution avant d'
finally:
avoir une chance de s'exécuter (par exemple, planter l'interpréteur, éteindre votre ordinateur, suspendre un générateur pour toujours).En voici quelques autres auxquels vous n'avez peut-être pas pensé:
Selon la façon dont vous quittez l'interpréteur, vous pouvez parfois "annuler" enfin, mais pas comme ça:
Utilisation du précaire
os._exit
(cela relève de "crash l'interpréteur" à mon avis):J'exécute actuellement ce code, pour tester s'il s'exécutera enfin après la mort par la chaleur de l'univers:
Cependant, j'attends toujours le résultat, alors revenez ici plus tard.
la source
finally
dans un générateur ou une coroutine peut facilement échouer à s'exécuter , sans se rapprocher d'une condition de "crash de l'interpréteur".sleep(1)
résulterait certainement un comportement indéfini. :-Dos._exit
c'est, à toutes fins pratiques, la même chose que d'induire un crash (sortie impure). La bonne façon de sortir estsys.exit
.Selon la documentation Python :
Il convient également de noter que s'il y a plusieurs instructions return, dont une dans le bloc finally, alors le retour de bloc finally est le seul qui s'exécutera.
la source
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 lever l'exception.
(ce que vous auriez pu contrôler vous-même en exécutant simplement le code de votre question)
Le seul cas où je peux imaginer où le bloc finally ne sera pas exécuté est lorsque l'interpréteur Python lui-même se bloque, par exemple dans le code C ou à cause d'une panne de courant.
la source
J'ai trouvé celui-ci sans utiliser de fonction générateur:
Le sommeil peut être n'importe quel code qui pourrait s'exécuter pendant des périodes de temps incohérentes.
Ce qui semble se produire ici, c'est que le premier processus parallèle à terminer quitte le bloc try avec succès, mais tente ensuite de renvoyer à partir de la fonction une valeur (foo) qui n'a été définie nulle part, ce qui provoque une exception. Cette exception tue la carte sans permettre aux autres processus d'atteindre leur bloc final.
Aussi, si vous ajoutez la ligne
bar = bazz
juste après l'appel sleep () dans le bloc try. Ensuite, le premier processus pour atteindre cette ligne lève une exception (parce que bazz n'est pas défini), ce qui provoque l'exécution de son propre bloc finally, mais tue ensuite la carte, faisant disparaître les autres blocs try sans atteindre leurs blocs finalement, et le premier processus à ne pas atteindre son instruction de retour non plus.Ce que cela signifie pour le multitraitement Python, c'est que vous ne pouvez pas faire confiance au mécanisme de gestion des exceptions pour nettoyer les ressources dans tous les processus si même l'un des processus peut avoir une exception. Un traitement de signal supplémentaire ou une gestion des ressources en dehors de l'appel de mappage multitraitement serait nécessaire.
la source
Addendum à la réponse acceptée, juste pour vous aider à voir comment cela fonctionne, avec quelques exemples:
Ce:
sortira
sortira
la source