atexit --- 終了ハンドラー


atexit モジュールは、終了ハンドラー を登録・解除する関数を定義します。 終了ハンドラーは、プログラムの正常終了時 (例えば sys.exit() が呼び出されたときや、メインモジュールの実行が完了したとき)、より一般的には interpreter shutdown 時に自動的に実行される関数です。

終了時には、登録されているすべての終了ハンドラーを登録した順序の 逆順 に呼び出します。 A、B、C の順に登録すると、インタープリタの終了時には C、B、A の順に実行します。 これは、通常は低水準のモジュールが高水準のモジュールより先にインポートされるので、後片付けは後で行う必要があるという想定に基づいています。

If an exception is raised during execution of an exit handler, a traceback is printed (unless SystemExit is raised) and the exception information is saved. After all exit handlers have had a chance to run, the last exception to be raised is re-raised.

複数のインタープリタを使うプログラムでは、各インタープリタが終了ハンドラーのスタックを持ちます。 ハンドラーは、そのインタープリタが終了するとき (例えば concurrent.interpreters.Interpreter.close() や C API の Py_EndInterpreter() による終了時) に実行されます。 このモジュールの登録用関数が影響するのは、呼び出し元のインタープリタだけです。

注意: Python が処理しないシグナルによってプログラムが強制終了した場合、Python 内部の致命的なエラーが検出された場合、または os._exit() が呼び出された場合、終了ハンドラーは呼び出されません。

注意: クリーンアップ関数内からの関数の登録・登録解除効果は定義されていません。

警告

終了ハンドラーを作成するとき、特に C API を使った拡張では、自身の後片付けが終わった後も、他の終了ハンドラーが任意の Python コードを実行する可能性があることに注意してください。 そのようなコードは正常に実行されるか例外で失敗するべきであり、クラッシュしてはいけません。

バージョン 3.12 で変更: 終了ハンドラー内で新しいスレッドを開始したり、os.fork() で新しいプロセスを作成したりしようとすると、RuntimeError を送出するようになりました。 以前は、Python ランタイムのメインスレッドがスレッド状態を解放する一方で、threading 内部のルーチンや新しいプロセスがその状態を使おうとして、競合状態が発生する可能性がありました。 その結果、正常に終了せずクラッシュすることがありました。

バージョン 3.7 で変更: サブインタープリタで使う場合、登録した関数は登録先のインタープリタに固有となりました。

atexit.register(func, *args, **kwargs)

func を終了ハンドラーとして登録します。 func に渡す引数があれば、register() の引数として渡してください。 同じ関数と引数を複数回登録することもできます。

この関数は func を返し、これをデコレータとして利用できます。

atexit.unregister(func)

終了ハンドラーのリストから func を削除します。 func が登録されていなければ、unregister() は何もせず、エラーも出しません。 func が複数回登録されていた場合、atexit の呼び出しスタックから、その関数をすべて削除します。 登録解除では内部で等価比較 (==) を使うため、関数の参照先が同一のオブジェクトである必要はありません。

参考

readline モジュール

readline の履歴ファイルを読み書きする、atexit の実用的な使用例です。

atexit の使用例

次の簡単な例では、モジュールのインポート時にファイルからカウンタの値を読み込んで初期化し、プログラムの終了時に更新後の値を自動保存する方法を示します。 アプリケーションが終了時にこのモジュールを明示的に呼び出す必要はありません。

try:
    with open('counterfile') as infile:
        _count = int(infile.read())
except FileNotFoundError:
    _count = 0

def incrcounter(n):
    global _count
    _count = _count + n

def savecounter():
    with open('counterfile', 'w') as outfile:
        outfile.write('%d' % _count)

import atexit

atexit.register(savecounter)

register() に指定した位置引数とキーワード引数は登録した関数を呼び出す際に渡されます:

def goodbye(name, adjective):
    print('Goodbye %s, it was %s to meet you.' % (name, adjective))

import atexit

atexit.register(goodbye, 'Donny', 'nice')
# または:
atexit.register(goodbye, adjective='nice', name='Donny')

デコレータ として利用する例:

import atexit

@atexit.register
def goodbye():
    print('You are now leaving the Python sector.')

デコレータとして利用できるのは、引数なしで呼び出せる関数に限られます。