atexit - Chiama funzioni quando un programma è in chiusura

Scopo Registra funzioni che saranno chiamate quando un programma è in chiusura
Versione Python 2.1.3 e successivo

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.

Esempi

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)

Quando non vengono chiamate le funzioni di atexit?

I callback registrati con atexit non sono invocati se

  • il programma muore a causa di un segnale
  • viene invocato direttamente os._exit()
  • un errore fatale in Python viene rilevato (nell'interprete)

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.

Eccezioni nei Callback di atexit

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:

atexit
La documentazione della libreria standard per questo modulo