Question 1Dans with X() as y:, la valeur liée à y est la valeur de retour de quelle méthode ?
Instruction with et gestionnaires de contexte — Ouverture/fermeture sûre avec __enter__ / __exit__
Apprends l'instruction with et les gestionnaires de contexte en Python. Le couple __enter__ / __exit__ pour une ouverture/fermeture sûre, et comment écrire le tien — pratique inclus.
La dernière fois on a travaillé sur la protection de l'état interne d'une classe. Cette fois on s'éloigne d'une couche — vers les ressources externes qui vivent en dehors du processus Python (fichiers, connexions DB, sockets réseau, verrous) — et on regarde l'instruction with et les gestionnaires de contexte qui gèrent leur acquisition et libération en sécurité.
Pourquoi tu as besoin de l'instruction with
Les opérations comme ouvrir un fichier ou se connecter à une base de données viennent avec un devoir de nettoyage : « une fois fini, ferme-le ». Oublie de fermer, et tu fuites des descripteurs de fichier, tu retiens des connexions DB pour toujours, et tu laisses le processus externe s'accrocher aux ressources aussi.
close(), l'autre côté continue d'attendre des instructions et de retenir la ressource.Tu peux écrire la même logique avec try / finally, mais alors chaque auteur doit se rappeler d'appeler close() dans finally à chaque fois. Quand le code grandit ou que plus de gens y touchent, quelqu'un oubliera — c'est juste la réalité.
L'instruction with enferme l'acquisition et la libération dans une unité syntaxique et les automatise. with open("file.txt") as f: est l'exemple canonique : le fichier est garanti de se fermer au moment où tu sors du bloc with.
Écrire ton propre gestionnaire de contexte — __enter__ et __exit__
Un objet utilisable avec with est appelé un gestionnaire de contexte. Pour transformer une classe en un, implémente juste deux méthodes spéciales.
- __enter__(self) — tourne quand tu entres dans le bloc with. Sa valeur de retour est liée à la variable nommée après as.
- __exit__(self, exc_type, exc_val, traceback) — tourne quand tu sors du bloc. Toujours appelée, sortie normale ou exceptionnelle.
Un exemple minimal de style connexion DB est ci-dessous (on n'utilise pas vraiment une bibliothèque DB — on l'imite avec des chaînes).
class DatabaseManager:
def __init__(self, db_name):
self.db_name = db_name
self.connection = None # pas encore connecté
def __enter__(self):
print(f"Connecting to {self.db_name}")
self.connection = f"connection_to_{self.db_name}" # vrai code : objet connexion réel
return self.connection # valeur liée à la variable as
def __exit__(self, exc_type, exc_val, traceback):
print(f"Disconnecting from {self.db_name}")
self.connection = None # nettoyage
return False # ne pas avaler les exceptions
with DatabaseManager("user_data_db") as conn:
print(f" active connection: {conn}")
print(" inserting data")
# ↑ quand ce bloc sort, __exit__ tourne
Lance-le et la sortie apparaît dans l'ordre « connexion → travail dans le bloc → déconnexion ». La déconnexion tourne sans que personne l'appelle explicitement — c'est toute la valeur de with. Le développeur est libéré de l'inquiétude de fermer la connexion.
Les trois arguments de __exit__ — Capturer les exceptions
__exit__ prend trois arguments : exc_type, exc_val, traceback. Python les utilise pour dire à __exit__ si une exception est survenue dans le bloc with.
- Sortie normale — les trois sont None. Nettoie juste.
- Sortie sur exception — exc_type est la classe d'exception, exc_val est l'instance, traceback est l'objet traceback.
La valeur de retour de __exit__ a aussi un sens. Retourner True avale l'exception — elle ne se propage pas au-delà du bloc. Retourner False / None la relève après le nettoyage. Le défaut devrait être False (ou ne rien return) : log ou notifie, mais laisse toujours l'exception s'échapper.
None × 3. Les exceptions livrent le triplet « classe / instance / traceback ». Un raise ValueError("invalid") concret rend les contenus tangibles.Retourner True depuis __exit__ tue silencieusement l'exception
Si __exit__ retourne True, l'exception dans with ne se propage pas. Tentant, mais l'appelant pense maintenant que l'opération a réussi — c'est un effet de bord sérieux. Par défaut False (ou pas de return) : log ou notifie si tu veux, mais laisse toujours l'exception remonter.
Maintenant déclenche vraiment une exception dans with et observe ce qui atteint les trois arguments de __exit__.
Comparé à try / finally — Pourquoi with gagne
Le travail d'un gestionnaire de contexte peut être fait avec try / finally. La raison de choisir with à la place est que « le couple ouverture/fermeture vit dans la classe ». Écrire la même tâche de deux manières rend la différence en volume de code de l'appelant et en clarté évidente.
# ❌ try / finally — l'appelant écrit le nettoyage à la main à chaque fois
db = DatabaseManager("shop_db")
conn = db.open() # méthode connect personnalisée
try:
use(conn) # vrai travail
finally:
db.close() # n'oublie pas — copié-collé partout
# ✅ with — l'ouverture/fermeture vit dans la classe, l'appelant ne fait que le travail
with DatabaseManager("shop_db") as conn:
use(conn) # pas besoin de finally
- Appelant — doit écrire
try/finallyà chaque fois - Oubli — un mauvais copier-coller et une fuite apparaît
- Coût de changement — des étapes de nettoyage en plus signifient éditer chaque site d'appel
- Appelant — une ligne,
with X() as y: - Oubli — impossible au niveau syntaxique (
__exit__tourne toujours) - Coût de changement — du nettoyage en plus signifie éditer seulement
__exit__
with ajoute. Quand les sites d'appel se multiplient, les changements de nettoyage restent dans une seule classe.Utilise with partout où acquisition et libération vont par paires
Fichiers, connexions DB, verrous, sockets réseau — partout où tu « prends une ressource au début et dois la rendre à la fin » — est un candidat pour with. La bibliothèque standard Python expose déjà beaucoup de ces choses comme gestionnaires de contexte : open(), threading.Lock(), sqlite3.connect(), etc.
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Quand une exception est levée dans un bloc with, qu'est-ce qui décrit correctement le comportement de __exit__ ?
Question 3Si __exit__ retourne True, qu'arrive-t-il à l'exception levée dans le bloc with ?