2.24. Context manager

L’istruzione with esegue del codice di inizializzazione, poi un corpo, poi del codice di pulizia – con la garanzia che la pulizia venga eseguita anche quando il corpo fallisce a metà. La coppia di metodi che fornisce l’inizializzazione e la pulizia è chiamata context manager.

La forma è:

with <expression> as <name>:
    <body>

L’espressione restituisce un context manager. Python ne chiama il metodo __enter__, opzionalmente lega il risultato a <name>, esegue il corpo e poi chiama __exit__ – sia che il corpo si sia completato normalmente sia che abbia sollevato un’eccezione.

Diagramma di flusso: __enter__ viene eseguito per primo, poi il corpo; il corpo o si completa normalmente o solleva un'eccezione; in entrambi i casi, __exit__ viene eseguito alla fine.

__exit__ viene eseguito alla fine del blocco qualunque cosa accada al suo interno.

2.24.1. Usare un context manager

L’esempio canonico è l’apertura di un file:

with open("data.txt") as f:
    text = f.read()

# f is now closed, even if read() failed

open() restituisce un oggetto file che è esso stesso un context manager; __enter__ restituisce il file, e __exit__ lo chiude. Il blocco with rende «chiudi sempre il file quando hai finito» il comportamento predefinito anziché qualcosa che il chiamante deve ricordarsi.

2.24.1.1. Più context manager

Una singola istruzione with può entrare in più context manager contemporaneamente, separati da virgole:

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

Equivale a due blocchi with annidati, ma più piatto. I manager vengono entrati da sinistra a destra e usciti in ordine inverso; se __enter__ sul manager di destra solleva un’eccezione, l”__exit__ del manager di sinistra viene comunque eseguito.

2.24.2. Scrivere un context manager

Qualsiasi classe con i metodi __enter__ e __exit__ funziona come context manager:

class Section:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        print("---", self.label, "---")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("--- end", self.label, "---")
        return False

with Section("setup"):
    print("doing the work")

Output:

--- setup ---
doing the work
--- end setup ---

__exit__ riceve tre argomenti che descrivono l’eccezione che ha terminato il blocco, oppure tre valori None se il blocco si è completato normalmente. Restituire False (o None) lascia propagare qualsiasi eccezione dopo la pulizia; restituire True la sopprimerebbe.

Usa i context manager per qualsiasi risorsa che abbia un ciclo di vita «apri / chiudi» o «acquisisci / rilascia» – non solo i file. Il pattern mantiene la pulizia abbinata all’inizializzazione nel punto in cui entrambe vengono introdotte, così che una chiusura dimenticata nel mezzo di una funzione lunga non possa far trapelare la risorsa.