Scopo | Registra funzioni che saranno chiamate quando un programma è in chiusura |
Versione Python | 2.1.3 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 atexit
Il modulo
atexit
fornisce una semplice interfaccia per registrare funzioni che saranno chiamate quando un programma si sta normalmente chiudendo. Anche il modulo
sys
fornisce un aggancio,
sys.exitfunc
, ma è possibile registrare solo una funzione. Il registro di
atexit
può essere usato simultaneamente da molteplici moduli e librerie.
Un semplice esempio per registrare una funzione tramite
atexit.register()
potrebbe essere questo:
import atexit
def all_done():
print 'all_done()'
print 'In registrazione'
atexit.register(all_done)
print 'Registrato'
Visto che il programma non fa null'altro,
all_done()
viene chiamata immediatamente.
$ python atexit_simple.py In registrazione Registrato all_done()
E' anche possibile registrare più di una funzione, e fornire argomenti. Questo può essere utile per disconnettersi in modo pulito da database, rimuovere file termporanei ecc.. Visto che è possibile passare argomenti alle funzioni registrate, non occorre neppure mantenere una lista separata di cose da pulire, basta registrare una funzione di pulizia più di una volta.
import atexit
def my_cleanup(name):
print 'my_cleanup(%s)' % name
atexit.register(my_cleanup, 'prima')
atexit.register(my_cleanup, 'seconda')
atexit.register(my_cleanup, 'terza')
Si noti in che l'ordine di chiamata delle funzioni in uscita è inverso rispetto a quello di registrazione. Questo consente di pulire i moduli nell'ordine inverso rispetto a quello di importazione (e di conseguenza di registrazione delle loro funzioni di atexit), il che dovrebbe ridurre i conflitti di dipendenza.
$ python atexit_multiple.py my_cleanup(terza) my_cleanup(seconda) my_cleanup(prima)
I callback registrati con atexit non sono invocati se
os._exit()
Per dimostrare come un programma viene ucciso da un segnale, si può modificare uno degli esempi dall'articolo su subprocess . Due file sono coinvolti, i programmi figlio e genitore. Il genitore fa partire il figlio, si mette in pausa, quindi lo uccide:
import os
import signal
import subprocess
import time
proc = subprocess.Popen('atexit_signal_child.py')
print 'GENITORE: In pausa prima di inviare il segnale...'
time.sleep(1)
print 'GENITORE: Invio segnale al figlio'
os.kill(proc.pid, signal.SIGTERM)
Il filgio imposta un callback atexit per provare che lo stesso non viene effettuato
import atexit
import time
import sys
def not_called():
print 'FIGLIO: il gestore di atexit non dovrebbe essere stato chiamato'
print 'FIGLIO: Registrazione del gestore atexit'
sys.stdout.flush()
atexit.register(not_called)
print 'FIGLIO: In pausa in attesa del segnale'
sys.stdout.flush()
time.sleep(5)
Quando viene eseguito, si dovrebbe ottenere qualcosa del tipo:
$ python atexit_signal_parent.py FIGLIO: Registrazione del gestore atexit FIGLIO: In pausa in attesa del segnale GENITORE: In pausa prima di inviare il segnale... GENITORE: Invio segnale al figlio
Si noti che lo script figlio (atexit_signal_child.py) non stampa il messaggio inserito nella funzione
not_called()
.
Alla stessa stregua, se un programma salta il normale percorso di uscita può evitare che venga chiamata il callback atexit.
import atexit
import os
def not_called():
print 'Questa non dovrebbe essere chiamata'
print 'Sto registrando ...'
atexit.register(not_called)
print 'Registrata'
print 'Sto uscendo...'
os._exit(0)
Visto che viene chiamato
os._exit(0)
, invece di uscire normalmente, il
callback
non viene effettuato.
$ python atexit_os_exit.py Sto registrando ... Registrata Sto uscendo...
Se si fosse usato
sys.exit()
il
callback
sarebbe comunque stata chiamato
import atexit
import sys
def all_done():
print 'all_done()'
print 'Sto registrando ...'
atexit.register(all_done)
print 'Registrata'
print 'Sto uscendo...'
sys.exit()
$ python atexit_sys_exit.py Sto registrando ... Registrata Sto uscendo... all_done()
La simulazione di un errore fatale nell'interprete Python viene lasciata al lettore come esercitazione.
I traceback risultanti da eccezioni sollevate dai callback atexit sono stampate alla console e l'ultima eccezione sollevata viene risollevata in modo da risultare il messaggio di errore finale del programma.
import atexit
def exit_with_exception(message):
raise RuntimeError(message)
atexit.register(exit_with_exception, 'Registrata per prima')
atexit.register(exit_with_exception, 'Registrata per seconda')
Ancora una volta si noti che l'ordine di registrazione controlla l'ordine di esecuzione. Se un errore in un callback introduce un errore in un altro (registrato prima, ma chiamato dopo), il messaggio di errore finale potrebbe non essere quello più utile da mostrare all'utente.
$ python atexit_exception.py Error in atexit._run_exitfuncs: Traceback (most recent call last): File "/usr/lib/python2.7/atexit.py", line 24, in _run_exitfuncs func(*targs, **kargs) File "atexit_exception.py", line 7, in exit_with_exception raise RuntimeError(message) RuntimeError: Registrata per seconda Error in atexit._run_exitfuncs: Traceback (most recent call last): File "/usr/lib/python2.7/atexit.py", line 24, in _run_exitfuncs func(*targs, **kargs) File "atexit_exception.py", line 7, in exit_with_exception raise RuntimeError(message) RuntimeError: Registrata per prima Error in sys.exitfunc: Traceback (most recent call last): File "/usr/lib/python2.7/atexit.py", line 24, in _run_exitfuncs func(*targs, **kargs) File "atexit_exception.py", line 7, in exit_with_exception raise RuntimeError(message) RuntimeError: Registrata per prima
In generale è probabile che si voglia gestire e registare tutte le eccezioni nelle proprie funzioni di pulizia, visto che fa confusione avere un programma che scarica errori in uscita.
Vedere anche: