Un gestionnaire de contexte est un objet qui définit le comportement d'entrée et de sortie d'un bloc with, assurant la gestion automatique des ressources (fichiers, connexions, etc.). Il est mis en œuvre à l'aide des méthodes __enter__ et __exit__ dans une classe ou via le décorateur @contextmanager du module contextlib.
class FileManager: def __init__(self, filename, mode): self.filename = filename self.mode = mode self.file = None def __enter__(self): self.file = open(self.filename, self.mode) return self.file def __exit__(self, exc_type, exc_val, exc_tb): if self.file: self.file.close() with FileManager('test.txt', 'w') as f: f.write('Hello')
Subtilités : Il est important de traiter correctement les exceptions dans __exit__, en renvoyant True pour supprimer les erreurs, et de faire attention aux ressources pour ne pas les laisser ouvertes en cas d'erreurs.
Question : Si une exception se produit dans le bloc with, Python appelle-t-il la méthode __exit__ ? Comment les paramètres de l'erreur sont-ils transmis ?
Réponse : Oui, la méthode __exit__ est toujours appelée, même s'il y a une exception. Les types d'exception, sa valeur et le traceback lui sont transmis. Si __exit__ retourne True, l'exception est ignorée.
class Simple: def __enter__(self): print('Entrée') def __exit__(self, exc_type, exc_val, exc_tb): print('Sortie') print(exc_type, exc_val) return True # l'erreur ne "sort pas" vers l'extérieur with Simple(): raise ValueError('boom!')
Histoire
__exit__ dans un gestionnaire de contexte fait maison pour gérer les descripteurs de fichiers - en cas d'exception, le fichier n'était pas fermé, ce qui entraînait des fuites de descripteurs de fichiers et des dysfonctionnements lors de la manipulation d'un grand nombre de fichiers.Histoire
True dans __exit__ pour toutes les exceptions. Cela "étouffait" les erreurs et entraînait des pannes invisibles et des violations d'intégrité des données, car la logique pensait que la transaction était réussie.Histoire
Utilisation du décorateur @contextmanager du module contextlib, mais oubli de traiter les exceptions à l'intérieur de yield, entraînant une connexion de socket non fermée lors de l'échec du code et le serveur "se figait" avec des ports ouverts.