Aller au contenu
Développement medium

Context managers Python : le mot-clé with (et contextlib)

8 min de lecture

logo python

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.

  • 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, with multiple).

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 oublier

Le 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, ici

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.

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.

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érieur

La 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.

É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 contextmanager
import time
@contextmanager
def 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.

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 suppress
import os
with suppress(FileNotFoundError):
os.remove("cache.tmp") # ne plante pas si le fichier est absent

Pour 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.

  • Un context manager prépare une ressource à l'entrée d'un bloc et la nettoie à la sortie.
  • Le mot-clé with garantit ce nettoyage, même en cas d'exception : plus d'oubli de close().
  • On crée un context manager avec une classe (__enter__/__exit__) ou avec @contextmanager (plus court).
  • __enter__ renvoie l'objet lié par as ; __exit__ libère la ressource et reçoit l'exception éventuelle.
  • __exit__ return True supprime l'exception (rare) ; par défaut elle se propage.
  • Un with gère plusieurs ressources à la fois ; contextlib.suppress ignore une erreur attendue.
  • Le cas d'usage type : fichiers, verrous, connexions et transactions de base de données.

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.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn