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.

Flow diagram: __enter__ runs first, then the body; the body either completes normally or raises; either way, __exit__ runs at the end.

__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.