Apprenez en lisant dans l'ordre

contextlib — Gestion de ressources et with personnalisé

Écrire son propre with sans classe grâce à @contextmanager et yield, garantir le nettoyage avec try/finally, et absorber des exceptions ciblées avec suppress.

contextlib est le module à utiliser quand tu veux écrire ton propre with. On va parcourir ses deux outils les plus simples dans l'ordre : @contextmanager pour la façon la plus simple d'en définir un, et suppress pour absorber des exceptions spécifiques.

@contextmanager — définir un with facilement

L'instruction with de Python est la façon sûre de gérer "les choses qu'il faut nettoyer après usage"open de fichier, connexions BD, acquisition de verrou, etc. Pour faire fonctionner une classe avec with à la main, tu dois définir __enter__ / __exit__, ce qui fait beaucoup de code répétitif. @contextmanager est un raccourci : écris un seul générateur (une fonction qui contient yield) et tu obtiens un gestionnaire de contexte qui marche avec with. Le code avant yield est "le début de l'action", et le code après est "le nettoyage".

Structure de @contextmanager
@contextmanagerdef section(name):Setup(avant yield)yieldTeardown(après yield)
Une fonction génératrice plus @contextmanager — la partie avant yield est l'équivalent de __enter__ (setup), et la partie après yield est l'équivalent de __exit__ (teardown). La valeur du yield est ce qui est lié par as dans l'instruction with.
from contextlib import contextmanager

# --- Référence : construire une classe compatible with ----------
# class Section:
#     def __init__(self, name):
#         self.name = name
#     def __enter__(self):
#         print(f"--- {self.name} début ---")
#         return self                 # valeur liée par le `as` de with
#     def __exit__(self, exc_type, exc, tb):
#         print(f"--- {self.name} fin ---")
# ----------------------------------------------------------

# Version @contextmanager : une fonction génératrice fait le même travail
@contextmanager
def section(name):
    print(f"--- {name} début ---")
    yield                       # le corps de with s'exécute ici
    print(f"--- {name} fin ---")

# Utilisation
with section("agrégation"):
    total = sum(range(100))
    print("Total :", total)

# Sortie :
# --- agrégation début ---
# Total : 4950
# --- agrégation fin ---
Version classe → simplification version @contextmanager
Version classe__init__ / __enter__ /__exit__ — 3 méthodesSimplifie avec@contextmanagerVersion générateuravant yield = setupaprès yield = teardown
Remplacer une classe avec __init__ / __enter__ / __exit__ (trois méthodes) par une seule fonction génératrice décorée par @contextmanager mappe le code avant/après yield directement à setup/teardown — bien moins de code.

Utilise try/finally pour garantir le nettoyage même sur exception

Le code après yield peut ne pas s'exécuter si une exception est levée à l'intérieur du bloc with. Pour un nettoyage qui doit toujours arriver — fermer des fichiers, libérer des verrous — enveloppe le corps du générateur dans try : yield finally : cleanup() pour la sécurité.

Construis un transaction(name) modélisant une transaction BD avec @contextmanager. Affiche BEGIN avant et COMMIT après, avec le SQL à l'intérieur du bloc visible entre les deux.

① Importe contextmanager depuis contextlib

② Définis une fonction transaction(name) décorée par @contextmanager — affiche [name] BEGIN, puis yield, puis affiche [name] COMMIT après

③ À l'intérieur de with transaction("order_create"):, affiche deux lignes SQL : INSERT INTO orders (id, total) VALUES (1, 1980) et UPDATE inventory SET stock = stock - 1

(Si ton code s'exécute correctement, l'explication apparaîtra.)

Éditeur Python

Exécuter le code pour voir le résultat

suppress — absorber des exceptions spécifiques

contextlib.suppress(ExceptionType, ...) est un gestionnaire de contexte pour absorber les exceptions spécifiées et continuer l'exécution. Le motif "ignore juste cette exception et passe à la suite" — équivalent à try : ... except KeyError : pass — tient en une seule ligne. Tu peux lister plusieurs types d'exceptions à la fois, et tout ce qui est en dehors de la liste se propage normalement.

from contextlib import suppress
import os

# "Exécute avec les valeurs par défaut même si le fichier n'existe pas"
config = {"theme": "light", "font_size": 14}
with suppress(FileNotFoundError):
    with open("user_config.txt") as f:
        config["theme"] = f.read().strip()
# Ne plante pas si user_config.txt manque — config garde ses valeurs par défaut
print(config)
# → {'theme': 'light', 'font_size': 14}

# "Saute silencieusement si déjà supprimé"
with suppress(FileNotFoundError):
    os.remove("old.tmp")
print("Suppression terminée (OK s'il n'existait pas)")

Utilise suppress pour continuer même quand tu extrais des clés manquantes d'un dict.

① Importe suppress depuis contextlib

② Définis un dict user = {"name": "Alice", "email": "alice@example.com"} (note : pas de clé age)

③ À l'intérieur de with suppress(KeyError):, essaie d'accéder à user["age"] — confirme que la clé manquante ne plante pas

④ Juste après le bloc, affiche On continue : rien n'a planté

⑤ Affiche le nom et l'email de l'utilisateur sur une seule ligne sous la forme Utilisateur : ◯ <◯>

Éditeur Python

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Quand tu décores une fonction génératrice avec @contextmanager, pourquoi doit-elle yielder exactement une fois ?

Question 2À l'intérieur de with suppress(KeyError):, que se passe-t-il quand KeyError est levée ?