L'histoire - en Python, pour gérer les ressources (fichiers, connexions, transactions), on utilise la structure with, qui est basée sur le protocole des gestionnaires de contexte (enter, exit). Pour les cas simples, écrire une classe entière est excessif, c'est pourquoi le décorateur @contextmanager (module contextlib) a été proposé, permettant de définir des gestionnaires de ressources sous forme de générateurs.
Problème - libérer ou fermer manuellement les ressources est inconfortable, le code devient instable aux erreurs (par exemple, oublier de fermer un fichier). Il n'est également pas souhaitable d'écrire une classe distincte avec deux méthodes pour des choses simples (comme un changement temporaire de répertoire ou de stdout).
Solution - utiliser @contextmanager pour décrire de manière concise "le début" et "la fin" de l'utilisation de la ressource, garantissant le traitement des exceptions et la libération.
Exemple de code :
from contextlib import contextmanager @contextmanager def open_file(filename, mode): f = open(filename, mode) try: yield f finally: f.close() with open_file('test.txt', 'w') as f: f.write('Hello')
Caractéristiques clés :
Peut-on faire en sorte qu'il y ait plusieurs objets retournés dans yield depuis @contextmanager (par exemple via un tuple) ?
Oui, c'est possible et même pratique pour retourner un "groupe" de ressources associées.
Exemple de code :
@contextmanager def managed_two(): a, b = [], {} try: yield a, b finally: a.clear(); b.clear()
Que se passe-t-il si une exception est levée après yield - la ressource se fermera-t-elle ?
Oui, le bloc finally s'exécutera de toute façon, même si une erreur/exception se produit dans le code à l'intérieur de with.
Est-ce que @contextmanager peut remplacer une classe de gestionnaire de contexte complète avec enter/exit ?
Dans la plupart des cas triviaux - oui, pour des cas plus complexes avec des états imbriqués ou de l'héritage, il est plus pratique de passer par une classe.
Ouvrir et fermer manuellement un fichier :
f = open('test.txt', 'w') try: f.write('Hello') finally: f.close()
Avantages :
Inconvénients :
Utiliser @contextmanager pour un changement temporaire de répertoire de travail ou ouverture de fichier (ou paramètres d'environnement) :
@contextmanager def work_in(dirname): import os prev = os.getcwd() os.chdir(dirname) try: yield finally: os.chdir(prev)
Avantages :
Inconvénients :