finally en Python : le code qui s'exécute quoi qu'il arrive

Le bloc finally s'exécute dans tous les cas, avec ou sans erreur, et même après un return : c'est là qu'on referme ce qui a été ouvert.
6 min de lecture
Believemy logo

Définition

Un programme qui ouvre quelque chose doit le refermer : un fichier, une connexion à une base, un verrou, un dossier temporaire. L'ennui, c'est que la ligne de fermeture arrive toujours après le travail, et qu'une erreur survenue pendant ce travail interrompt tout avant qu'on l'atteigne. La ressource reste ouverte, et personne ne s'en aperçoit avant le jour où le serveur refuse de nouvelles connexions.

finally répond exactement à ce problème. Ce bloc termine une structure try, et Python s'engage à l'exécuter dans tous les cas : le travail s'est bien passé, une exception a été rattrapée par un except, une exception n'a été rattrapée par personne, ou la fonction est sortie plus tôt que prévu par un return. Aucun autre bloc du langage n'offre cette garantie.

PYTHON
connexion = ouvrir_connexion()
try:
    connexion.executer(requete)
except TimeoutError:
    journaliser("Trop long")
finally:
    connexion.fermer()   # appelé que la requête passe ou non

La connexion est donc rendue dans les trois scénarios possibles : la requête aboutit, elle expire et le except la rattrape, ou elle échoue pour une raison imprévue. Écrire connexion.fermer() une ligne plus bas, hors de la structure, couvrirait les deux premiers cas mais pas le troisième : l'exception remonterait aussitôt et emporterait la fermeture avec elle.

C'est là tout ce que finally sait faire, et cela change la façon d'écrire : le nettoyage passe dans une zone que rien ne peut sauter, et on cesse de se demander par où le programme risque de s'échapper.


Il s'exécute même après un return

Voici le comportement qui surprend le plus, et c'est aussi celui qui rend le bloc réellement utile. On imagine volontiers qu'un return met fin à la fonction sur-le-champ. Ce n'est pas ce qui se passe.

PYTHON
def lire(chemin):
    fichier = open(chemin)
    try:
        return fichier.read()   # 1. la valeur est lue, puis mise de côté
    finally:
        fichier.close()         # 2. le nettoyage a lieu ensuite
                                # 3. et la valeur part seulement après

Python évalue d'abord l'expression qui suit le return, met le résultat de côté, exécute le finally, et rend la valeur à la toute fin. La question qui suit est légitime : le nettoyage peut-il abîmer ce qui va être renvoyé ? Non, la lecture est déjà terminée, et fermer le fichier après coup ne retire rien au texte qu'on vient d'en tirer.

La même mécanique s'applique à break et à continue à l'intérieur d'une boucle : la sortie est préparée, le finally s'intercale, puis la sortie a lieu.

Attention

Ne placez jamais de return à l'intérieur du finally lui-même. Il écrase la valeur préparée par le try, et surtout il fait disparaître une exception en train de remonter : l'erreur n'apparaît plus nulle part, ni dans les journaux ni dans le traceback, et la fonction renvoie tranquillement un résultat faux. Un raise ou un break écrits là produisent le même effacement silencieux.


Ce que with fait à sa place

Le couple try et finally revient si souvent autour des fichiers et des connexions que Python a fini par l'emballer dans une écriture dédiée : le gestionnaire de contexte, qu'on emploie avec le mot-clé with.

PYTHON
# Le with contient déjà un finally, écrit une fois pour toutes dans open()
with open("donnees.csv") as fichier:
    contenu = fichier.read()

Les deux formes ne sont pas concurrentes : le with déclenche un finally caché, fourni par l'objet lui-même. Dès que la chose manipulée sait se refermer toute seule, préférez cette écriture, plus courte et impossible à oublier.

finally reprend la main pour tout ce qui n'est pas un objet fermable, et ces cas restent fréquents : un compteur à remettre à zéro, un réglage global à restaurer, une ligne de fin à écrire dans les journaux avec logging, un chronomètre à arrêter. Aucun gestionnaire de contexte n'existe pour eux, et le bloc explicite reste le bon outil.


L'ordre exact d'exécution

Quand un try réunit plusieurs blocs, dire de tête qui passe avant qui devient vite hasardeux. Le tableau ci-dessous donne la séquence pour les quatre situations que vous rencontrerez, chaque ligne se lisant de gauche à droite comme un déroulé dans le temps.

SituationCe qui s'exécute, dans l'ordre
Aucune erreurtry, puis else, puis finally
Erreur rattrapéetry jusqu'à l'erreur, puis except, puis finally
Erreur non rattrapéetry jusqu'à l'erreur, puis finally, puis l'erreur remonte
Sortie par returntry jusqu'au return, puis finally, puis la valeur part

Retenez surtout la dernière ligne, car c'est elle qui fait la différence une fois en production : le nettoyage a lieu avant que l'appelant ne reçoive quoi que ce soit. Renvoyer le contenu d'un fichier depuis un try tout en le fermant dans le finally est donc parfaitement sûr.

Un point souvent absent des explications : une erreur survenue à l'intérieur du except, pendant qu'on traite la première, déclenche elle aussi le finally avant de remonter. Le bloc de secours n'échappe pas à la règle.


Questions fréquentes

Question

finally s'exécute-t-il si le programme plante vraiment ?

Sur une exception non rattrapée, oui : le bloc s'exécute entièrement, puis l'exception poursuit sa remontée jusqu'à l'affichage du traceback. Les seules situations qui l'en empêchent échappent au langage : un processus tué par le système d'exploitation, une coupure de courant, ou un appel direct à os._exit(). Aucun code Python ne peut les intercepter.

Question

Peut-on avoir un finally sans except ?

Oui, un try suivi directement d'un finally est parfaitement valide, et cette forme est même très courante. Elle dit une chose précise : je ne prétends pas savoir traiter cette erreur, je la laisse remonter vers le code qui saura, mais je tiens à ranger derrière moi avant qu'elle ne parte.

Question

Quelle différence avec le bloc else du try ?

Le else ne s'exécute que si le try s'est déroulé sans la moindre erreur, alors que finally s'exécute dans tous les cas. Le premier sert à poursuivre un traitement qui dépend de la réussite, le second à libérer ce qui a été pris. Les deux cohabitent très bien dans la même structure, l'else passant toujours avant le finally.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.