atexit - Callback alla Chiusura di un Programma
Scopo: Registra una o più funzioni (callback) che saranno chiamate quando un programma è in chiusura
Il modulo atexit fornisce una interfaccia per registrare funzioni che saranno chiamate quando un programma si sta normalmente chiudendo.
Registrare Callback in Uscita
Il seguente è un esempio per registrare una funzione chiamando esplicitamente register()
:
# atexit_simple.py
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.
$ python3 atexit_simple.py In registrazione Registrato all_done()
E' anche possibile registrare più di una funzione, e fornire argomenti alle funzioni registrate. Questo può essere utile per disconnettersi in modo pulito da database, rimuovere file temporanei ecc.. Invece che mantenere una lista di risorse che devono essere liberate, si può registrare una funzione di pulizia separata per ogni risorsa.
# atexit_multiple.py
import atexit
def my_cleanup(name):
print('my_cleanup({})'.format(name))
atexit.register(my_cleanup, 'primo')
atexit.register(my_cleanup, 'secondo')
atexit.register(my_cleanup, 'terzo')
L'ordine di chiamata delle funzioni in uscita è inverso rispetto a quello di registrazione. Questo metodo consente di pulire i moduli nell'ordine inverso rispetto a quello di importazione (e di conseguenza di registrazione delle loro funzioni atexit
), il che dovrebbe ridurre i conflitti di dipendenza.
$ python3 atexit_multiple.py my_cleanup(terzo) my_cleanup(secondo) my_cleanup(primo)
Sintassi per Decoratore
Le funzioni che non richiedono argomenti possono essere registrate utilizzando register()
come un decoratore. Questa sintassi alternativa è conveniente per funzioni di pulizia che operano su dati globali a livello di modulo.
# atexit_decorator.py
import atexit
@atexit.register
def all_done():
print('all_done()')
print('partenza programma principale')
Visto che la funzione è registrata così com'è definita, è anche importante assicurarsi che essa funzioni regolarmente anche se nessun altra attività viene eseguita dal modulo. Se le risorse che si suppone debbano essere pulite non sono mai inizializzate, la chiamata della funzione di uscita non dovrebbe produrre un errore.
$ python3 atexit_decorator.py partenza programma principale all_done()
Cancellare Callback
Per cancellare un callback di uscita, lo si rimuova dal registro usando unregister()
.
# atexit_unregister.py
import atexit
def my_cleanup(name):
print('my_cleanup({})'.format(name))
atexit.register(my_cleanup, 'primo')
atexit.register(my_cleanup, 'secondo')
atexit.register(my_cleanup, 'terzo')
atexit.unregister(my_cleanup)
Tutte le chiamate allo stesso callback sono cancellate, a prescindere da quante volte è stato registrato.
$ python3 atexit_unregister.py
La rimozione di un callback che non era stato precedentemente registrato non viene considerato un errore.
# atexit_unregister_not_registered.py
import atexit
def my_cleanup(name):
print('my_cleanup({})'.format(name))
if False:
atexit.register(my_cleanup, 'mai registrato')
atexit.unregister(my_cleanup)
Visto che ignora in modo silente callback sconosciuti, unregister()
può essere usato anche quando la sequenza delle registrazioni potrebbe non essere nota.
$ python3 atexit_unregister_not_registered.py
Quando Ci Sono Callback Di atexit Non Chiamati?
I callback registrati con atexit non sono chiamati se si verifica almeno una delle seguenti condizioni:
- Il programma termina a causa di un segnale
os._exit()
viene invocato direttamente- Viene rilevato un errore fatale nell'interprete
Un esempio dall'articolo su subprocess può essere aggiornato per mostrare cosa succede quando un programma viene terminato da un segnale. Sono coinvolti due file, il programma genitore e il figlio. Il genitore fa partire il figlio, si mette in pausa, quindi lo termina.
# atexit_signal_parent.py
import os
import signal
import subprocess
import time
proc = subprocess.Popen(['python3', './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 figlio imposta un callback atexit di uscita, poi si mette in pausa fino a che arriva il segnale.
# atexit_signal_child.py
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 eseguito, questo è il risultato.
$ python3 atexit_signal_parent.py GENITORE: In pausa prima di inviare il segnale... FIGLIO: Registrazione del gestore atexit FIGLIO: In pausa in attesa del segnale GENITORE: Invio segnale al figlio
Il figlio non stampa il messaggio inserito in not_called()
.
Se il programma usa os._exit()
, può evitare la chiamata dei callback atexit
.
# atexit_os_exit.py
import atexit
import os
def not_called():
print('Questa non dovrebbe essere chiamata')
print('In registrazione')
atexit.register(not_called)
print('Registrato')
print('In uscita...')
os._exit(0)
Visto che l'esempio salta il normale percorso di uscita, il callback non viene eseguito. Anche la stampa del risultato non viene scaricata, quindi l'esempio è eseguito con l'opzione -u
per abiltare l'I/O senza buffer.
$ python3 -u atexit_os_exit.py In registrazione Registrato In uscita...
Per assicurarsi che i callback vengano eseguiti, si consenta al programma di terminare esaurendo le istruzioni da eseguire oppure chiamando sys.exit()
.
# atexit_sys_exit.py
import atexit
import sys
def all_done():
print('all_done()')
print('In registrazione')
atexit.register(all_done)
print('Registrato')
print('In uscita...')
sys.exit()
Questo esempio chiama sys.exit()
, quindi i callback registrati vengono chiamati.
$ python3 atexit_sys_exit.py In registrazione Registrato In uscita... all_done()
Gestire Eccezioni
I traceback per le eccezioni sollevate in atexit sono stampati alla console e l'ultima eccezione sollevata viene risollevata in modo da essere l'ultimo messaggio di errore del programma.
# atexit_exception.py
import atexit
def exit_with_exception(message):
raise RuntimeError(message)
atexit.register(exit_with_exception, 'Registrato per primo')
atexit.register(exit_with_exception, 'Registrato per secondo')
L'ordine di registrazione controlla l'ordine di esecuzione. Se un errore in un callback ne introduce un errore in un altro (registrato in precedenza ma chiamato successivamente), il messaggio di errore finale potrebbe non essere quello più utile da mostrare all'utente.
$ python3 atexit_exception.py Error in atexit._run_exitfuncs: Traceback (most recent call last): File "atexit_exception.py", line 6, in exit_with_exception raise RuntimeError(message) RuntimeError: Registrato per secondo Error in atexit._run_exitfuncs: Traceback (most recent call last): File "atexit_exception.py", line 6, in exit_with_exception raise RuntimeError(message) RuntimeError: Registrato per primo
Vedere anche:
- atexit
- La documentazione della libreria standard per questo modulo
- Gestione delle eccezioni (di prossima traduzione)
- Gestione globale delle eccezioni non catturate
- Note di portabilità per atexit