Vai al contenuto
eLearner.app
Modulo 9 · Lezione 3 di 435/36 nel corso~12 min
Lezioni del modulo (3/4)

Context manager: with e __enter__/__exit__

Un context manager è un oggetto che usi con with. Garantisce che certe operazioni di setup/cleanup avvengano sempre, anche in presenza di errori. L'esempio canonico: aprire e chiudere un file.

Il pattern with

Python
with open("note.txt", "w") as f:
    f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezione

Tradotto in italiano: "con la risorsa X aperta come f, fai questo. Al termine (qualunque cosa accada), chiudi X." Senza with dovresti scrivere un try/finally ogni volta.

Più di uno alla volta

Python
with open("a.txt") as f, open("b.txt") as g:
    dati_a = f.read()
    dati_b = g.read()

Scrivere il proprio context manager (classe)

Serve definire due metodi dunder: __enter__ e __exit__.

Python
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) viene chiamato all'ingresso del blocco; il valore ritornato è ciò che ottieni dopo as.
  • __exit__(self, exc_type, exc_value, tb) viene chiamato all'uscita (sempre). Se ritorna True, eventuali eccezioni vengono soppresse.

La via breve: @contextmanager

contextlib.contextmanager ti permette di scrivere un context manager come funzione generatore:

Python
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))

Il codice prima dello yield è __enter__, quello dopo è __exit__. Il try/finally garantisce il cleanup anche in caso di errori.

suppress: ignora un'eccezione

Python
from contextlib import suppress

with suppress(FileNotFoundError):
    open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propaga

Pitfall: with solo per oggetti che lo supportano

Python
x = 42
with x:  # AttributeError: __enter__
    ...

Scrivere context manager con @contextmanager

Il modulo contextlib della libreria standard offre un decoratore estremamente comodo chiamato @contextmanager per definire context manager senza dover scrivere intere classi con i metodi __enter__ e __exit__. Basta definire una funzione generatore con un'istruzione yield:

Python
from contextlib import contextmanager

@contextmanager
def gestisci_risorsa():
    # codice __enter__
    yield risorsa
    # codice __exit__

Prova tu

Esercizio#python.m9.l3.e1
Tentativi: 0Caricamento…

Usa contextlib.suppress per provare a fare int('non-numero') ignorando ValueError. Dopo il blocco assegna `ok = True`. Valuta `ok`.

Caricamento editor…
Mostra suggerimento

suppress(ValueError) ingoia solo ValueError.

Soluzione disponibile dopo 3 tentativi

Esercizio di ripasso

Esercizio#python.m9.l3.e2
Tentativi: 0Caricamento…

Scrivi un context manager `collect` con @contextmanager che faccia yield di una lista vuota e, all'uscita, la converta in tupla (stampala). Usalo per appendere 1, 2, 3 alla lista nel blocco. Alla fine, valuta `len(collection)` dove `collection` è la lista yieldata.

Caricamento editor…
Mostra suggerimento

yield items per esporre la lista nel blocco with.

Soluzione disponibile dopo 3 tentativi

Sfida aggiuntiva

Esercizio#python.m9.l3.e3
Tentativi: 0Caricamento…

Scrivi un context manager personalizzato usando il decoratore `@contextmanager` da `contextlib`. Il context manager deve chiamarsi `mock_session`. All'ingresso deve inserire `'start'` in una lista globale `log_list = []`, cedere il controllo con `yield`, e all'uscita appendere `'stop'`. Valuta `log_list` dopo aver usato il context manager con `with mock_session():`.

Caricamento editor…
Mostra suggerimento

Usa yield all'interno di mock_session per cedere il controllo prima di aggiungere 'stop'.

Soluzione disponibile dopo 3 tentativi