Leçons du module (3/4)
Context managers : with et __enter__/__exit__
Un gestionnaire de contexte (context manager) est un objet que vous utilisez avec with. Il garantit que certaines opérations de configuration/nettoyage ont toujours lieu, même en cas d'erreurs. L'exemple canonique : ouvrir et fermer un fichier.
Le patron with
with open("note.txt", "w") as f:
f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezioneEn langage clair : "avec la ressource X ouverte sous le nom f, fais ceci. Une fois terminé (quoi qu'il arrive), ferme X." Sans with, vous devriez écrire un try/finally à chaque fois.
Plus d'un à la fois
with open("a.txt") as f, open("b.txt") as g:
dati_a = f.read()
dati_b = g.read()Écrire votre propre gestionnaire de contexte (classe)
Vous devez définir deux méthodes dunder : __enter__ et __exit__.
class Timer:
def __enter__(self):
import time
self.t0 = time.perf_counter()
return self # è l'oggetto che finisce dopo "as"
def __exit__(self, exc_type, exc_value, traceback):
import time
self.elapsed = time.perf_counter() - self.t0
# ritornare True ingoia l'eccezione; di solito non lo vuoi
return False
with Timer() as t:
sum(range(10**6))
t.elapsed # quanto è durato__enter__(self)est appelée à l'entrée du bloc ; la valeur renvoyée est ce que vous obtenez aprèsas.__exit__(self, exc_type, exc_value, tb)est appelée à la sortie (toujours). Si elle renvoieTrue, les exceptions sont supprimées.
Le raccourci : @contextmanager
contextlib.contextmanager vous permet d'écrire un gestionnaire de contexte sous forme de fonction générateur :
from contextlib import contextmanager
@contextmanager
def timer():
import time
t0 = time.perf_counter()
try:
yield # qui sta il blocco "with"
finally:
elapsed = time.perf_counter() - t0
print(f"durata: {elapsed:.4f}s")
with timer():
sum(range(10**6))Le code avant yield correspond à __enter__, le code après à __exit__. Le bloc try/finally garantit le nettoyage même en cas d'erreurs.
suppress : ignorer une exception
from contextlib import suppress
with suppress(FileNotFoundError):
open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propagaPiège : with uniquement pour les objets qui le prennent en charge
x = 42
with x: # AttributeError: __enter__
...Écrire des gestionnaires de contexte avec @contextmanager
Le module contextlib de la bibliothèque standard exporte un décorateur très pratique appelé @contextmanager pour créer des gestionnaires de contexte à l'aide de fonctions générateurs, au lieu d'écrire des classes verbeuses implémentant les méthodes __enter__ et __exit__. Il vous suffit d'écrire une fonction générateur utilisant un simple yield :
from contextlib import contextmanager
@contextmanager
def manage_resource():
# __enter__ code
yield resource
# __exit__ codeÀ vous de jouer
Utilisez contextlib.suppress pour essayer int('non-numero') en ignorant ValueError. Après le bloc, affectez `ok = True`. Évaluez `ok`.
Afficher l'indice
suppress(ValueError) n'intercepte que ValueError.
Solution disponible après 3 tentatives
Exercice de révision
Écrivez un gestionnaire de contexte `collect` avec @contextmanager qui renvoie une liste vide et, à la sortie, la convertit en tuple (affichez-le). Utilisez-le pour ajouter 1, 2, 3 à la liste à l'intérieur du bloc. À la fin, évaluez `len(collection)` où `collection` est la liste renvoyée.
Afficher l'indice
Utilisez yield items pour exposer la liste à l'intérieur du bloc with.
Solution disponible après 3 tentatives
Défi supplémentaire
Écrivez un gestionnaire de contexte personnalisé en utilisant le décorateur `@contextmanager` de `contextlib`. Le gestionnaire de contexte doit être nommé `mock_session`. À l'entrée, il doit ajouter `'start'` à une liste globale `log_list = []`, céder le contrôle avec `yield`, et à la sortie ajouter `'stop'`. Enfin, évaluez `log_list` après avoir utilisé le gestionnaire de contexte avec `with mock_session():`.
Afficher l'indice
Utilisez yield à l'intérieur de mock_session pour suspendre l'exécution avant d'ajouter 'stop'.
Solution disponible après 3 tentatives