atexit – プログラムの終了時に関数を呼び出す

目的:プログラムの終了時に呼び出される関数を登録する
利用できるバージョン:2.1.3 以上

atexit モジュールは、プログラムが正常終了するときに呼び出される関数を登録するためのシンプルなインタフェースを提供します。 sys モジュールにも sys.exitfunc() というフックを提供しますが、その関数で登録できるのは1つの関数のみです。 atexit モジュールで登録すると同時に複数のモジュールやライブラリで使用できます。

サンプル

atexit.register() 経由で関数を登録する簡単なサンプルは次のようになります。

import atexit

def all_done():
    print 'all_done()'

print 'Registering'
atexit.register(all_done)
print 'Registered'

このプログラムは他に何もしないので、すぐに all_done() が呼び出されます。

$ python atexit_simple.py
Registering
Registered
all_done()

さらに1つ以上の関数を登録したり引数を渡すこともできます。一時ファイルを削除する等、データベースからクリーンに切断するのに便利です。登録された関数へ引数を渡すこともできるので、クリーンアップするモノをリストに独立させて保持する必要もありません。つまり、その都度クリーンアップ関数を登録すれば良いだけです。

import atexit

def my_cleanup(name):
    print 'my_cleanup(%s)' % name

atexit.register(my_cleanup, 'first')
atexit.register(my_cleanup, 'second')
atexit.register(my_cleanup, 'third')

exit 関数が呼びされる順序は、登録された順序とは逆の順序で呼び出されることに注意してください。これは依存による競合を減らすために、インポートされた(関数を atexit に登録された)順序の逆でモジュールにクリーンアップさせます。

$ python atexit_multiple.py
my_cleanup(third)
my_cleanup(second)
my_cleanup(first)

どんなときに atexit 関数が呼び出されないの?

atexit で登録されたコールバック関数が実行されないのは次のような場合です。

  • プログラムがシグナルで終了する場合
  • os._exit() が直接実行される場合
  • Python の(インタープリタで)致命的エラーが検出される場合

シグナルで kill されるプログラムを説明するために、 subprocess モジュールの記事からサンプルの1つを変更します。親と子の2つのプログラムが実行されます。親プログラムは子プログラムを開始して、一時中断して、その子プログラムを kill します。

import os
import signal
import subprocess
import time

proc = subprocess.Popen('atexit_signal_child.py')
print 'PARENT: Pausing before sending signal...'
time.sleep(1)
print 'PARENT: Signaling child'
os.kill(proc.pid, signal.SIGTERM)

子プログラムは、コールバック関数が呼び出されないことを証明するために atexit にコールバック関数を登録します。

import atexit
import time
import sys

def not_called():
    print 'CHILD: atexit handler should not have been called'

print 'CHILD: Registering atexit handler'
sys.stdout.flush()
atexit.register(not_called)

print 'CHILD: Pausing to wait for signal'
sys.stdout.flush()
time.sleep(5)

実行すると、その実行結果は次のようになります。

$ python atexit_signal_parent.py
CHILD: Registering atexit handler
CHILD: Pausing to wait for signal
PARENT: Pausing before sending signal...
PARENT: Signaling child

子プログラムは登録した not_called() のメッセージを表示しないことを確認してください。

同様に、プログラムが正常終了しない場合に atexit のコールバック関数は実行されません。

import atexit
import os

def not_called():
    print 'This should not be called'

print 'Registering'
atexit.register(not_called)
print 'Registered'

print 'Exiting...'
os._exit(0)

正常終了する代わりに os._exit() を呼び出すので、登録されたコールバック関数は実行されません。

$ python atexit_os_exit.py

もし代わりに sys.exit() を使用した場合、登録されたコールバック関数も実行されます。

import atexit
import sys

def all_done():
    print 'all_done()'

print 'Registering'
atexit.register(all_done)
print 'Registered'

print 'Exiting...'
sys.exit()
$ python atexit_sys_exit.py
Registering
Registered
Exiting...
all_done()

同様に、Python インタープリタの致命的エラーは読者の練習問題として残しておきます。

atexit コールバック関数の例外

atexit のコールバック関数内で発生した例外のトレースバック情報はコンソールに表示されます。そして、最後に発生した例外がプログラムの最後のエラーメッセージになり再発生します。

import atexit

def exit_with_exception(message):
    raise RuntimeError(message)

atexit.register(exit_with_exception, 'Registered first')
atexit.register(exit_with_exception, 'Registered second')

登録された順序が実行順序を制御することに再注目してください。1つのコールバック関数のエラーがもう一方のエラーを発生させる場合(先に登録されても後に呼び出される)、最後のエラーメッセージはユーザへ表示する適切なエラーメッセージではない可能性があります。

$ python atexit_exception.py
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/atexit.py", line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File "atexit_exception.py", line 37, in exit_with_exception
    raise RuntimeError(message)
RuntimeError: Registered second
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/atexit.py", line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File "atexit_exception.py", line 37, in exit_with_exception
    raise RuntimeError(message)
RuntimeError: Registered first
Error in sys.exitfunc:
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/atexit.py", line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File "atexit_exception.py", line 37, in exit_with_exception
    raise RuntimeError(message)
RuntimeError: Registered first

一般的に、終了時にプログラムのダンプエラーを持つのは厄介なので、クリーンアップ関数内で全ての例外を完全にログ出力すると良いかもしれません。

See also

atexit
本モジュールの標準ライブラリドキュメント
Bookmark and Share