I gestori di contesto consentono di automatizzare e controllare l'esecuzione di blocchi di codice con garanzia di finalizzazione delle risorse, ad esempio, chiudere automaticamente file, rilasciare gestori di transazioni in database o bloccare/sbloccare thread. In Python standard, questo paradigma è stato introdotto per una gestione sicura e conveniente delle risorse esterne senza la necessità di monitorare manualmente la fase finale di lavoro.
Storia:
Prima dell'introduzione dei gestori di contesto, era necessario chiamare esplicitamente i metodi close()/release() per liberare le risorse, il che portava a errori se si verificavano eccezioni. Con l'introduzione della costruzione with ... as ...:, Python ha permesso di definire "l'area di attività della risorsa" esplicitamente, richiamando automaticamente i metodi per l'inizializzazione e il completamento del lavoro con essa.
Problema:
La gestione manuale della "chiusura" della risorsa è pericolosa: se si dimentica di close() (o release()), le risorse rimarranno occupate fino al termine del processo o addirittura bloccate per sempre. Questo è particolarmente critico nei casi di lavoro con file, connessioni di rete, transazioni in database.
Soluzione:
I gestori di contesto sono implementati tramite i metodi magici enter() e exit(). All'ingresso nel blocco with, Python chiama enter, e all'uscita exit, anche se all'interno del blocco si verifica un'eccezione.
Esempio di codice:
class ManagedFile: def __init__(self, filename): self.filename = filename self.file = None def __enter__(self): self.file = open(self.filename, 'w') return self.file def __exit__(self, exc_type, exc_val, exc_tb): if self.file: self.file.close() # È possibile sopprimere l'eccezione return True, di solito return False with ManagedFile('demo.txt') as f: f.write('Hello, world!\n') # il file è garantito chiuso
Caratteristiche chiave:
Si può utilizzare lo stesso oggetto gestore di contesto più volte di seguito in diversi with?
No: di solito la risorsa viene aperta in enter, rilasciata in exit, e l'oggetto diventa non valido per un riutilizzo. È meglio creare un nuovo oggetto per ogni blocco with.
Cosa fare se è necessario utilizzare più risorse in un solo with?
È possibile separare le variabili con una virgola:
with open('a.txt') as fa, open('b.txt') as fb: ...
oppure utilizzare contextlib.ExitStack per casi complessi.
Qual è la differenza tra scrivere un gestore di contesto come classe con enter/exit e come generatore con il decoratore @contextmanager?
Il decoratore @contextmanager del modulo contextlib consente di implementare il gestore in modo più semplice, tramite yield:
from contextlib import contextmanager @contextmanager def open_file(name): f = open(name) try: yield f finally: f.close() with open_file('file.txt') as f: ...
Lo sviluppatore apre un file tramite open(), scrive in esso e dimentica di chiamare close() — il file può rimanere aperto (soprattutto in caso di errori), i dati non vengono registrati.
Vantaggi:
Svantaggi:
È stato utilizzato ovunque with open() come standard (o risorsa gestita personalizzata), il gestore di contesto implementa correttamente il rilascio della risorsa.
Vantaggi:
Svantaggi: