exception-handling try - Est-ce que 'enfin' s'exécute toujours en Python?




catch all (5)

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 personnels montrent que finally sont exécutés 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 échouer à s'exécuter en Python?


Answers

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'exception ou dans le bloc else et qu'une nouvelle exception est levée, le code dans le bloc final est toujours exécuté.

Il convient également de noter que s'il y a plusieurs déclarations de retour, y compris une dans le bloc finally, alors le retour du bloc finally est le seul à s'exécuter.


"Garanti" est un mot beaucoup plus fort que toute mise en œuvre mérite finally . Ce qui est garanti, c'est que si l'exécution sort de l'ensemble try - finally construire, elle passera finally par le faire. Ce qui n'est pas garanti, c'est que l'exécution sortira de l' try - finally .

  • Un finally dans un générateur ou une corotine asynchrone pourrait ne jamais fonctionner , si l'objet ne s'exécute jamais à la fin. Il y a beaucoup de façons qui pourraient arriver; 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 compliqué: lorsque le générateur est collecté, Python tente d'exécuter le bloc finally en lançant une exception GeneratorExit , mais ici nous capturons cette exception et yield nouveau, à quel point Python imprime un avertissement (" générateur ignoré GeneratorExit ") et abandonne. Voir PEP 342 (Coroutines via Enhanced Generators) pour plus de détails.

    D'autres façons qu'un générateur ou une coroutine pourrait ne pas exécuter jusqu'à la conclusion incluent si l'objet est simplement GC'ed (oui, c'est possible, même dans CPython), ou si async with __aexit__ dans __aexit__ , ou si l'objet await s ou yield s dans un bloc finally . Cette liste n'est pas destinée à être exhaustive.

  • Un thread de démon peut finally ne jamais s'exécuter si tous les threads non démon sortent en premier.

  • os._exit arrêtera le processus immédiatement sans exécuter de blocs finally .

  • os.fork peut provoquer l' exécution deux fois des blocs. En plus des problèmes normaux que vous pouvez vous attendre à ce que deux choses se produisent deux fois, cela peut entraîner des conflits d'accès simultanés (panne, blocage, ...) si l'accès aux ressources partagées n'est pas correctement synchronisé .

    Comme le multiprocessing utilise fork-without-exec pour créer des processus de travail en utilisant la méthode fork start (par défaut sur Unix), puis appelle os._exit dans le worker une fois le travail du worker terminé, l'interaction multiprocessing peut être problématique ( example ).

  • Une erreur de segmentation de niveau C empêchera l'exécution des blocs.
  • kill -SIGKILL empêchera finally blocs de fonctionner. SIGTERM et SIGHUP empêcheront également l'exécution de blocs à 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 celui où l'utilisateur frappe control-C juste au moment où nous commençons à exécuter le bloc finally . Python déclenche un KeyboardInterrupt et ignore chaque ligne du contenu du bloc finally . ( KeyboardInterrupt code KeyboardInterrupt -safe est très difficile à écrire).
  • Si l'ordinateur perd de la puissance, ou s'il hiberne et ne se réveille pas, les blocs ne fonctionneront pas.

Le bloc finally n'est pas un système de transaction; il ne fournit pas de garanties 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 et compter sur finally pour trop.


Eh bien, oui et non.

Ce qui est garanti, c'est que Python essayera 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 d'élever l'exception.

(ce que vous auriez pu contrôler vous-même en exécutant simplement le code dans votre question)

Le seul cas que je puisse imaginer où le bloc finally ne sera pas exécuté est quand l'interpréteur Python se bloque par exemple à l'intérieur du code C ou à cause d'une coupure de courant.


Oui. Enfin, gagne toujours.

La seule façon de le vaincre est de stopper l'exécution avant d'avoir finally: une chance de l'exécuter (par exemple, planter 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é.

En voici un couple de plus auquel 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 os._exit précaire (cela tombe sous "crash l'interprète" à mon avis):

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

Je cours actuellement ce code, pour tester si finalement sera toujours exécuté après la mort thermique de l'univers:

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

Cependant, j'attends toujours le résultat, alors revenez ici plus tard.


Le bloc finally est toujours exécuté sauf en cas de cessation anormale du programme, résultant d'un crash de la machine virtuelle Java ou d'un appel à System.exit(0) .

De plus, toute valeur renvoyée à l'intérieur du bloc finally remplacera la valeur renvoyée avant l'exécution du bloc finally. Veillez donc à vérifier tous les points de sortie lors de l'utilisation de try finally.





python exception-handling try-catch-finally finally