Menu
Français
Essayer dans le Playground

Gestionnaires de contexte Python : l'instruction with, expliquée

Ce que fait vraiment l'instruction with — nettoyage automatique pour fichiers, verrous, connexions de base de données, et tout ce qui a besoin d'une fermeture fiable.

L'instruction qui nettoie après elle-même

Chaque ressource que tu ouvres dans un programme — un fichier, une connexion réseau, un handle de base de données, un verrou — doit être fermée quand tu as fini. Oublie, et tu fais fuir de la mémoire, tu retiens des verrous qui bloquent d'autres processus, ou tu corromps des fichiers en cas de crash. L'instruction with de Python gère ça pour toi.

Le motif par lequel tout le monde commence, c'est lire un fichier :

with open("notes.txt") as f:
    contents = f.read()
    print(contents)

Deux choses se passent automatiquement. À l'entrée, open() te donne un objet fichier lié à f. À la sortie — que le bloc se termine normalement, revienne tôt, ou lève une exception — Python appelle f.close() pour toi.

C'est tout. C'est tout l'intérêt de with.

Ce que « with » remplace

Avant les gestionnaires de contexte, le code sûr équivalent était un try/finally :

f = open("notes.txt")
try:
    contents = f.read()
    print(contents)
finally:
    f.close()

Cinq lignes de cérémonie pour « lire un fichier et le fermer quand terminé ». Multiplie ça par chaque open dans un programme plus grand et tu commences à voir l'attrait. with est plus court, plus difficile à rater, et impossible d'oublier le nettoyage.

Ouvrir plusieurs ressources

Tu peux lier plusieurs gestionnaires de contexte dans un seul with :

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read().upper())

Les deux fichiers s'ouvrent à l'entrée, les deux se ferment à la sortie. Si le premier open réussit et que le second lève une exception, Python ferme quand même le premier — la mécanique gère correctement la configuration partielle.

Pour des listes plus longues de ressources, la forme à parenthèses (Python 3.10+) est plus claire :

with (
    open("a.txt") as a,
    open("b.txt") as b,
    open("c.txt") as c,
):
    ...

Ce qu'est vraiment un gestionnaire de contexte

Tout objet qui définit __enter__ et __exit__ est un gestionnaire de contexte. Le protocole est très simple :

  • __enter__(self) s'exécute quand le bloc with commence. Sa valeur de retour est ce à quoi as name se lie.
  • __exit__(self, exc_type, exc_value, traceback) s'exécute quand le bloc se termine, peu importe comment. Si une exception a causé la sortie, les infos d'exception sont passées pour que le gestionnaire de contexte puisse les inspecter ou les supprimer.

Voici un minimal qui chronomètre le bloc qu'il enveloppe :

main.py
Output
Click Run to see the output here.

with Timer(): crée l'objet, appelle son __enter__, exécute le corps, appelle __exit__. Pas de fichier, pas de verrou — juste un petit wrapper autour de « fais quelque chose, mesure combien de temps ça a pris ».

Le raccourci contextlib.contextmanager

Définir une classe pour chaque gestionnaire de contexte est plus lourd qu'il ne faut. contextlib.contextmanager transforme une fonction génératrice en gestionnaire de contexte — un yield sépare le « avant » du « après » :

main.py
Output
Click Run to see the output here.

Tout ce qui est avant yield est le comportement __enter__. Tout ce qui est après est le __exit__. Le try/finally fait que le nettoyage s'exécute même si le corps lève une exception.

La plupart des gestionnaires de contexte personnalisés que tu écriras entrent dans cette forme. Utilise la forme décorateur d'abord ; descends à une classe seulement quand tu as besoin de quelque chose que la forme générateur ne peut pas exprimer.

Changer quelque chose temporairement

Une forme courante : régler quelque chose, l'utiliser, le restaurer. Les gestionnaires de contexte expriment ça proprement :

main.py
Output
Click Run to see the output here.

Tout motif « régler, puis restaurer » — variables d'environnement, verbosité du logging, feature flags, fixtures de test — entre naturellement dans un gestionnaire de contexte. Les appelants n'ont pas à se rappeler de restaurer quoi que ce soit.

Supprimer les exceptions

La méthode __exit__ peut renvoyer True pour dire à Python « j'ai géré l'exception ; avale-la ». C'est rare et généralement une odeur, mais c'est comme ça que marche contextlib.suppress :

main.py
Output
Click Run to see the output here.

suppress(FileNotFoundError) transforme le FileNotFoundError en no-op. Utilise-le pour des opérations vraiment optionnelles — « essaie ça, peu importe si ça ne marche pas ». Ne l'utilise pas pour faire taire des exceptions auxquelles tu n'as pas réfléchi.

D'autres gestionnaires de contexte que tu rencontreras

Les gestionnaires de contexte apparaissent partout dans la bibliothèque standard une fois que tu commences à regarder :

import threading
from pathlib import Path

# Verrous — garantissent la libération même si la section critique lève une exception.
lock = threading.Lock()
with lock:
    ...

# tempfile — supprime le fichier temporaire quand tu as fini.
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
    path = Path(tmp) / "scratch.txt"
    path.write_text("hello")

# Connexions de base de données — ferme la connexion (ou termine la transaction).
import sqlite3
with sqlite3.connect(":memory:") as conn:
    conn.execute("CREATE TABLE t (x INTEGER)")

Les bibliothèques tierces suivent les mêmes conventions. Quand tu vois with something as x:, ça veut presque toujours dire « utilise x pendant la durée de ce bloc, et nettoie après ».

Quand ne pas utiliser with

  • Quand tu n'as pas vraiment de configuration et de nettoyage. Envelopper du code arbitraire dans un gestionnaire de contexte sans raison ajoute du bruit.
  • Quand tu as besoin de la ressource sur plusieurs blocs sans rapport. Garder un with ouvert pour toute la durée d'un long script peut cacher la vraie portée du nettoyage. Envisage plutôt une classe qui possède la ressource.
  • Quand un décorateur convient mieux. Certains motifs répétés (retry, log, chronométrage) se lisent plus naturellement comme @decorator sur une fonction que comme with ...: à l'intérieur. Choisis ce qui se lit le mieux au site d'appel.

La plupart du temps, with est le bon choix. Les rares exceptions sont faciles à repérer une fois que tu cherches.

Ensuite : travailler avec de vrais fichiers

Tu connais maintenant la mécanique derrière with open(...) as f: — qui est le contexte dans lequel tu l'utiliseras quatre-vingt-dix pour cent du temps. Le prochain chapitre le met au travail à lire, écrire et naviguer dans des fichiers sur le disque.

Questions fréquentes

Que fait with open en Python ?

with open(path) as f: ouvre le fichier et le lie à f pendant toute la durée du bloc. Quand le bloc se termine — normalement ou à cause d'une exception — Python ferme automatiquement le fichier. Tu n'as pas besoin de f.close() ; l'instruction with le garantit.

Pourquoi utiliser with plutôt que open() seul ?

Parce que with ferme le fichier même quand une exception se déclenche au milieu du bloc. Un simple open() te laisse la charge de te rappeler close() dans chaque chemin de code, y compris les chemins d'erreur. with est plus sûr et plus court.

Comment ouvrir plusieurs fichiers avec une seule instruction with ?

Sépare les gestionnaires de contexte par des virgules : with open('a.txt') as a, open('b.txt') as b:. Les deux fichiers sont ouverts à l'entrée et fermés à la sortie, dans l'ordre inverse. Ça remplace les instructions with imbriquées quand tu as besoin de plusieurs ressources à la fois.

Apprendre à coder avec Coddy

COMMENCER