ProgrammationDéveloppeur backend Python

Parlez-nous de l'utilisation des gestionnaires de contexte et des gestionnaires de ressources via le décorateur @contextmanager du module contextlib : pourquoi est-il nécessaire, comment cela fonctionne-t-il et quels pièges y a-t-il ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

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 :

  • Début du bloc - tout jusqu'à yield, qui renvoie la ressource.
  • Fin du bloc - après yield ; impératif avec gestion des erreurs et fermeture/libération.
  • Garantie de fermeture de la ressource même en cas d'exception.

Questions piégées.

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.

Erreurs typiques et anti-modèles

  • Omettre le bloc finally, ce qui entraîne une fuite de ressource en cas d'erreurs.
  • Yield n'est pas unique ; si la fonction contient deux yield - cela entraînera un RuntimeError.
  • Essayer d'utiliser @contextmanager avec des fonctions non-génératrices (pas de yield).

Exemples de la vie réelle

Cas négatif

Ouvrir et fermer manuellement un fichier :

f = open('test.txt', 'w') try: f.write('Hello') finally: f.close()

Avantages :

  • Contrôle total sur la gestion du cycle de vie de la ressource.

Inconvénients :

  • Beaucoup de code standard, plus de chances de faire des erreurs en oubliant le finally.

Cas positif

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 :

  • Concision, garantie de retour à l'état initial.

Inconvénients :

  • Un haut niveau d'abstraction peut masquer les détails de gestion de la ressource.