
Un context manager est un objet qui gère automatiquement une ressource : il prépare quelque chose à l'entrée d'un bloc et le nettoie à la sortie, quoi qu'il arrive. C'est le mécanisme derrière le mot-clé with que vous utilisez sans doute déjà avec open : le fichier se ferme tout seul à la fin du bloc, même si une erreur survient. Ce guide explique le fonctionnement du with, montre comment créer vos propres context managers avec une classe (__enter__/__exit__) ou avec le décorateur @contextmanager, et présente les outils du module contextlib.
Il s'adresse aux développeurs qui manipulent des ressources à libérer : fichiers, connexions réseau, verrous, transactions de base de données. Le fil conducteur : garantir que le nettoyage a toujours lieu, sans dépendre de votre vigilance. Tous les exemples ont été exécutés avec Python 3.12.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre ce que fait vraiment le mot-clé
with. - Créer un context manager avec une classe (
__enter__/__exit__). - Créer un context manager avec
@contextmanager, plus court. - Comprendre comment le nettoyage est garanti même en cas d'exception.
- Utiliser les outils de
contextlib(suppress,withmultiple).
Le problème : libérer une ressource à coup sûr
Section intitulée « Le problème : libérer une ressource à coup sûr »Quand on ouvre un fichier, une connexion ou un verrou, il faut le refermer, même si le code plante entre-temps. Sans with, on écrit un bloc try/finally verbeux, et il est facile d'oublier le nettoyage :
f = open("data.txt")try: traiter(f.read())finally: f.close() # à ne surtout pas oublierLe mot-clé with encapsule ce schéma. Il garantit l'appel du nettoyage à la sortie du bloc, et le code devient bien plus lisible :
with open("data.txt") as f: traiter(f.read())# le fichier est fermé automatiquement, iciComment fonctionne le with
Section intitulée « Comment fonctionne le with »Le with s'appuie sur un protocole à deux méthodes. À l'entrée du bloc, Python appelle __enter__ (préparation), et à la sortie __exit__ (nettoyage), quelle que soit la façon dont on sort du bloc. L'objet renvoyé par __enter__ est celui lié par le mot-clé as.
C'est exactement ce que fait open : __enter__ ouvre le fichier et le renvoie, __exit__ le ferme. N'importe quel objet respectant ce protocole peut être utilisé dans un with.
Créer un context manager avec une classe
Section intitulée « Créer un context manager avec une classe »Pour écrire le vôtre, définissez une classe avec __enter__ et __exit__. Prenons une connexion fictive à gérer proprement.
class Connexion: def __enter__(self): print("ouverture de la connexion") return self # objet lié par `as`
def __exit__(self, exc_type, exc_val, traceback): print("fermeture de la connexion") return False # ne masque pas une exception
def requete(self, sql): print(f"exécution : {sql}")
with Connexion() as conn: conn.requete("SELECT 1")À l'exécution, l'ordre est : ouverture, requête, puis fermeture. La méthode __enter__ prépare la ressource et la renvoie ; __exit__ la libère.
exit et les exceptions
Section intitulée « exit et les exceptions »Le point qui rend les context managers si utiles : __exit__ est appelé même si une exception est levée dans le bloc. C'est ce qui garantit qu'une ressource n'est jamais laissée ouverte.
try: with Connexion() as conn: raise ValueError("problème")except ValueError: print("erreur gérée à l'extérieur")
# Sortie :# ouverture de la connexion# fermeture de la connexion <- le nettoyage a bien eu lieu# erreur gérée à l'extérieurLa méthode __exit__ reçoit trois arguments décrivant l'exception éventuelle (exc_type, exc_val, traceback), tous à None si tout s'est bien passé. Sa valeur de retour décide du sort de l'exception :
return False(ou rien) : l'exception continue de se propager. C'est le comportement normal.return True: l'exception est supprimée. À réserver à des cas précis, car masquer une erreur silencieusement est dangereux.
Créer un context manager avec @contextmanager
Section intitulée « Créer un context manager avec @contextmanager »Écrire une classe pour un besoin simple est verbeux. Le décorateur @contextmanager du module contextlib permet de créer un context manager à partir d'une fonction génératrice : le code avant le yield prépare, le code après nettoie.
from contextlib import contextmanagerimport time
@contextmanagerdef chrono(label): debut = time.perf_counter() try: yield # le bloc with s'exécute ici finally: duree = time.perf_counter() - debut print(f"{label} : {duree:.3f}s")
with chrono("traitement"): somme = sum(range(1_000_000))Le yield marque l'endroit où s'exécute le bloc with. Le try/finally garantit que le nettoyage a lieu même en cas d'erreur, comme le ferait __exit__. Cette forme s'appuie sur les générateurs et convient à la plupart des cas simples.
Gérer plusieurs ressources et contextlib
Section intitulée « Gérer plusieurs ressources et contextlib »Un seul with peut ouvrir plusieurs ressources, séparées par des virgules. Elles sont fermées dans l'ordre inverse de leur ouverture.
with open("entree.txt") as src, open("sortie.txt", "w") as dst: dst.write(src.read())Le module contextlib fournit aussi des outils prêts à l'emploi. Le plus courant est suppress, qui ignore proprement une exception attendue, à la place d'un try/except vide :
from contextlib import suppressimport os
with suppress(FileNotFoundError): os.remove("cache.tmp") # ne plante pas si le fichier est absentPour un nombre de ressources connu seulement à l'exécution, contextlib.ExitStack permet d'empiler dynamiquement des context managers et de tous les fermer proprement à la sortie.
À retenir
Section intitulée « À retenir »- Un context manager prépare une ressource à l'entrée d'un bloc et la nettoie à la sortie.
- Le mot-clé
withgarantit ce nettoyage, même en cas d'exception : plus d'oubli declose(). - On crée un context manager avec une classe (
__enter__/__exit__) ou avec@contextmanager(plus court). __enter__renvoie l'objet lié paras;__exit__libère la ressource et reçoit l'exception éventuelle.__exit__return Truesupprime l'exception (rare) ; par défaut elle se propage.- Un
withgère plusieurs ressources à la fois ;contextlib.suppressignore une erreur attendue. - Le cas d'usage type : fichiers, verrous, connexions et transactions de base de données.
FAQ : les context managers en Python
Section intitulée « FAQ : les context managers en Python »with :with open("fichier.txt") as f:
contenu = f.read()
# ici, le fichier est déjà fermé, automatiquement
L'exemple le plus connu est open, qui ferme le fichier tout seul à la fin du bloc, même en cas d'erreur. Verrous, connexions réseau et transactions de base de données suivent le même principe.with encadre l'usage d'une ressource : il déclenche la préparation à l'entrée du bloc et le nettoyage à la sortie, quoi qu'il arrive.# sans with : verbeux et fragile
f = open("data.txt")
try:
f.read()
finally:
f.close()
# avec with : concis et sûr
with open("data.txt") as f:
f.read()
Il évite d'oublier de fermer un fichier ou de libérer un verrou, et remplace un bloc try/finally plus lourd.__enter__ et __exit__ :class Connexion:
def __enter__(self):
print("ouverture")
return self # valeur liée par `as`
def __exit__(self, exc_type, exc_val, tb):
print("fermeture") # nettoyage, toujours exécuté
return False # ne masque pas l'exception
with Connexion() as c:
...
__enter__ prépare la ressource, __exit__ la libère. Cette dernière reçoit les infos d'une éventuelle exception et s'exécute dans tous les cas.@contextmanager transforme une fonction génératrice en context manager, plus court qu'une classe :from contextlib import contextmanager
import time
@contextmanager
def chrono(label):
debut = time.perf_counter()
try:
yield # le bloc with s'exécute ici
finally:
print(f"{label}: {time.perf_counter() - debut:.3f}s")
with chrono("calcul"):
lourd_traitement()
Le code avant yield prépare, celui après (dans un finally) nettoie. Idéal pour les cas simples.__exit__) est appelé avant que l'exception ne remonte :try:
with open("data.txt") as f:
raise ValueError("boom")
except ValueError:
pass
# le fichier a bien été fermé malgré l'erreur
Le fichier est fermé, le verrou libéré, quoi qu'il arrive. Par défaut, l'exception continue de se propager ; elle n'est supprimée que si __exit__ renvoie True (rare et à manier avec prudence).with gère plusieurs ressources :with open("entree.txt") as src, open("sortie.txt", "w") as dst:
dst.write(src.read())
Le module contextlib ajoute des outils pratiques, comme suppress pour ignorer proprement une erreur attendue :from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("cache.tmp") # ne plante pas si le fichier est absent
C'est plus lisible qu'un try/except vide.Prochaines étapes
Section intitulée « Prochaines étapes »Les context managers gèrent des ressources : fichiers, générateurs et connexions de base de données en sont les usages les plus fréquents.