Scopo | Utilità per creare e lavorare con gestori di contesto |
Versione Python | 2.5 e successivo |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per il modulo contextlib
Il modulo contextlib contiene utilità per lavorare con gestori di contesto e con l'istruzione with .
__future__
Un gestore di contesto (context manager) è responsabile per una risorsa all'interno di un blocco di codice, che possibilmente dallo stesso viene creata quando il flusso del codice entra nel blocco, quindi viene ripulita quando il flusso esce dal blocco. Ad esempio, gli oggetti file supportano l'API del gestore di contesto per facilitare l'assicurarsi che essi vengano chiusi dopo che tutte le operazioni di lettura e scrittura sono terminate.
with open('/tmp/pymotw.txt', 'wt') as f:
f.write('il contenuto va qui')
# il file viene chiuso automaticamente
Un
gestore di contesto
viene abilitato dall'istruzione
with
, e l'API coinvolge due metodi:
__enter__()
viene eseguito quando il flusso di esecuzione entra nel blocco di codice all'interno di
with
. Esso ritorna un oggetto da utilizzare all'interno del contesto. Quando il flusso di esecuzione abbandona il blocco
with
, viene chiamato il metodo
__exit__()
del gestore di contesto per ripulire qualsivoglia risorsa sia stata utilizzata.
class Context(object):
def __init__(self):
print '__init__()'
def __enter__(self):
print '__enter__()'
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print '__exit__()'
with Context():
print 'Lavoro eseguito nel contesto'
La combinazione di un gestore di contesto e dell'istruzione
with
costituisce un modo più compatto di scrivere un blocco
try:finally
, visto che il metodo
__exit__()
del gestore di contesto viene sempre chiamato, anche se viene sollevata una eccezione.
$ python contextlib_api.py __init__() __enter__() Lavoro eseguito nel contesto __exit__()
Il metodo
__enter__()
può restituire un qualsiasi oggetto che venga associato ad un nome specificato nella clausola
as
dell'istruzione
with
. In questo esempio,
Context
ritorna un oggetto che utilizza il contesto aperto.
class DentroIlContesto(object):
def __init__(self, context):
print 'DentroIlContesto.__init__(%s)' % context
def do_something(self):
print 'DentroIlContesto.do_something()'
def __del__(self):
print 'DentroIlContesto.__del__'
class Context(object):
def __init__(self):
print 'Context.__init__()'
def __enter__(self):
print 'Context.__enter__()'
return DentroIlContesto(self)
def __exit__(self, exc_type, exc_val, exc_tb):
print 'Context.__exit__()'
with Context() as c:
c.do_something()
Può confondere un tantino, ma il valore associato alla variabile
c
è l'oggetto restituito da
__enter__()
e
non
dall'istanza di
Context
creata nell'istruzione
with
.
$ python contextlib_api_other_object.py Context.__init__() Context.__enter__() DentroIlContesto.__init__(<__main__.Context object at 0x7f649bca8750>) DentroIlContesto.do_something() Context.__exit__() DentroIlContesto.__del__
Il metodo
__exit__()
riceve argomenti che contengono dettagli di qualsiasi eccezione venga sollevata all'interno del blocco
with
.
class Context(object):
def __init__(self, handle_error):
print '__init__(%s)' % handle_error
self.handle_error = handle_error
def __enter__(self):
print '__enter__()'
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print '__exit__(%s, %s, %s)' % (exc_type, exc_val, exc_tb)
return self.handle_error
with Context(True):
raise RuntimeError('messaggio di errore gestito')
print
with Context(False):
raise RuntimeError('messaggio di errore propagato')
Se il gestore di contesto può gestire l'eccezione,
__exit__()
dovrebbe restituire un valore
True
per indicare che l'eccezione non deve propagarsi. Restituire
False
fa sì che l'eccezione sia sollevata dopo che
__exit__()
ritorna.
$ python contextlib_api_error.py __init__(True) __enter__() __exit__(, messaggio di errore gestito, ) __init__(False) __enter__() __exit__( , messaggio di errore propagato, ) Traceback (most recent call last): File "contextlib_api_error.py", line 24, in raise RuntimeError('messaggio di errore propagato') RuntimeError: messaggio di errore propagato
Creare gestori di contesto nel modo tradizionale, scrivendo una classe che abbia i metodi
__enter__()
ed
__exit__()
non è difficile. Tuttavia qualche volta può essere inutilmente dispendioso rispetto alla necessità di gestire un pezzo triviale di contesto. In questo tipo di situazioni si potrebbe utilizzare il decoratore
contextmanager()
per convertire una funzione generatore in un gestore di contesto.
import contextlib
@contextlib.contextmanager
def make_context():
print ' in entrata'
try:
yield {}
except RuntimeError, err:
print ' ERRORE:', err
finally:
print ' in uscita'
print 'Normale:'
with make_context() as value:
print ' dentro l\'istruzione with:', value
print
print 'Errore gestito:'
with make_context() as value:
raise RuntimeError('si mostra un esempio di gestione di un errore')
print
print 'Errore non gestito:'
with make_context() as value:
raise ValueError('questa eccezione non viene gestita')
Il generatore dovrebbe inizializzare il contesto, trattenerlo esattamente una volta, quindi ripulire il contesto. Il valore trattenuto, se esiste, viene legato alla variabile nella clausola as dell'istruzione with . Le eccezioni all'interno del blocco with sono nuovamente sollevate all'interno del generatore, in modo che ivi possano essere trattate.
$ python contextlib_contextmanager.py Normale: in entrata dentro l'istruzione with: {} in uscita Errore gestito: in entrata ERRORE: si mostra un esempio di gestione di un errore in uscita Errore non gestito: in entrata in uscita Traceback (most recent call last): File "contextlib_contextmanager.py", line 28, inraise ValueError('questa eccezione non viene gestite') ValueError: questa eccezione non viene gestita
A volte risulta necessario gestire simultaneamente contesti multipli (tipo quando si copiano dati da handle di file in input ed output, ad esempio). E' possibile annidare istruzioni with una all'interno dell'altra. Se il contesto più esterno non necessita del suo proprio blocco separato, tuttavia, si deve aggiungere un livello di indentazione senza avere un reale beneficio. Utilizzando nested() i contesti vengono annidati usando una singola istruzione with
import contextlib
@contextlib.contextmanager
def make_context(name):
print 'in entrata:', name
yield name
print 'in uscita :', name
with contextlib.nested(make_context('A'), make_context('B'), make_context('C')) as (A, B, C):
print 'all\'interno dell\'istruzione with:', A, B, C
Si noti che si esce dai contesti nell'ordine inverso rispetto a quello di entrata.
$ python contextlib_nested_with.py in entrata: A in entrata: B in entrata: C all'interno dell'istruzione with: A B C in uscita : C in uscita : B in uscita : A
In Python 2.7 e superiore, nested() è deprecato visto che l'istruzione with supporta l'annidamento direttamente.
import contextlib
@contextlib.contextmanager
def make_context(name):
print 'in entrata:', name
yield name
print 'in uscita :', name
with contextlib.nested(make_context('A'), make_context('B'), make_context('C')) as (A, B, C):
print 'all\'interno dell\'istruzione with:', A, B, C
Ciascun gestore di contesto e clausola opzionale
as
è separato da un virgola
,
. L'effetto è simile a quello con l'utilizzo di
nested()
, ma evita alcuni dei casi limite riguardo alla gestione di errori che
nested()
non può implementare correttamente.
$ python contextlib_nested_with.py entering: A entering: B entering: C all'interno dell'istruzione with: A B C exiting : C exiting : B exiting : A
La classe
file
supporta direttamente l'API del gestore di contesto, ma taluni altri oggetti che rappresentano
handle
aperti non lo fanno. L'esempio fornito nella documentazione della libreria standard per
contextlib
è l'oggetto restituito da
urllib.urlopen()
. Ci sono altre classi
legacy
che utilizzano un metodo
close()
ma non supportano l'API del gestore di contesto. Per assicurarsi che l'
handle
venga chiuso, si usi
closing()
per creare un gestore di contesto per la situazione.
import contextlib
class Door(object):
def __init__(self):
print ' __init__()'
def close(self):
print ' close()'
print 'Esempio Normale:'
with contextlib.closing(Door()) as door:
print ' dentro l\'istruzione with'
print
print 'Esempio di gestione errore:'
try:
with contextlib.closing(Door()) as door:
print ' sollevata da dentro l\'istruzione with'
raise RuntimeError('messaggio di errore')
except Exception, err:
print ' Si è verificato un errore:', err
L' handle viene chiuso a prescindere che si verifichi un errore nel blocco with .
$ python contextlib_clong.py Esempio Normale: __init__() dentro l'istruzione with close() Esempio di gestione errore: __init__() sollevata da dentro l'istruzione with close() Si è verificato un errore: messaggio di errore
Vedere anche: