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.
connexion = ouvrir_connexion()
try:
connexion.executer(requete)
except TimeoutError:
journaliser("Trop long")
finally:
connexion.fermer() # appelé que la requête passe ou nonLa 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.
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èsPython é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.
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.
# 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.
| Situation | Ce qui s'exécute, dans l'ordre |
|---|---|
| Aucune erreur | try, puis else, puis finally |
| Erreur rattrapée | try jusqu'à l'erreur, puis except, puis finally |
| Erreur non rattrapée | try jusqu'à l'erreur, puis finally, puis l'erreur remonte |
| Sortie par return | try 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
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.
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.
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.