閉じる

Pythonのfinallyは本当に必ず実行される?挙動と例外的なケース

Pythonのfinally句は「必ず実行される」とよく説明されますが、実は正しくは「ほぼ必ず実行される」と言うべき機能です。

本記事では、まずtry/except/finallyの基本挙動を丁寧に整理し、そのうえで「なぜほぼ必ずなのか」「どんな場合に実行されないのか」を、サンプルコードや図解を交えながら詳しく解説します。

現場で使えるベストプラクティスまで一通り押さえ、安心してクリーンアップ処理を書けるようになることを目指します。

finallyとは?Pythonにおける基本的な役割

try/except/finallyの基本構造と実行順序

Pythonのfinallyは、tryブロックの後処理を担当する「最後に必ず実行される」ことを意図したブロックです。

構文としては、次のような組み合わせ方があります。

  • try ... finally
  • try ... except ... finally
  • try ... except ... else ... finally

典型的な全パターンを含んだ構造は次のようになります。

Python
try:
    # 例外発生の可能性がある処理
    ...
except ValueError:
    # ValueError が発生したときの処理
    ...
except Exception as e:
    # その他の例外をまとめて処理
    ...
else:
    # try 内で例外が発生しなかった場合だけ実行
    ...
finally:
    # 例外の有無に関わらず、最後に実行される後始末
    ...

実行順序は次のように整理できます。

  1. tryブロックが実行される。
  2. 例外が起きなければelseがあればそれが実行される。
  3. 例外が起きた場合は、対応するexceptがあればそれが実行される。
  4. finallyブロックが最後に実行される。
  5. その後、returnや未処理の例外伝播など、関数からの復帰や制御フローが続行される。

どの経路を通ったとしても、制御がfinallyまで到達できる限り、必ず最後に実行されるというのがポイントです。

シンプルな実行順序のデモ

Python
def simple_flow():
    print("1: try の前")

    try:
        print("2: try の中")
    except Exception:
        print("3: except の中")
    else:
        print("3: else の中 (例外なし)")
    finally:
        print("4: finally の中")

    print("5: 関数の末尾")


if __name__ == "__main__":
    simple_flow()
実行結果
1: try の前
2: try の中
3: else の中 (例外なし)
4: finally の中
5: 関数の末尾

この例では例外が発生していませんが、finallyelseの後に必ず実行されていることが分かります。

後始末処理とリソース解放にfinallyを使う理由

finally の典型的な用途は、ファイルやソケットなどの「リソースの解放」や「後始末処理」です。

例えばファイルを開いたまま例外が発生すると、明示的に閉じなければファイルハンドルが残り続ける可能性があります。

Python
def read_file(path):
    f = None
    try:
        f = open(path, mode="r", encoding="utf-8")
        # ここで例外が発生するかもしれない
        data = f.read()
        return data
    except FileNotFoundError:
        print("ファイルが見つかりませんでした")
        return None
    finally:
        # f が開かれていれば必ず閉じる
        if f is not None:
            print("ファイルを閉じます")
            f.close()

このようにしておけば、読み取り処理の途中で例外が発生しても必ずclose()が呼ばれるため、ファイルが開きっぱなしになることを防げます。

「どの経路を通っても必ずやりたい処理」finallyに書く、というのが基本的な考え方です。

Pythonのfinallyは「ほぼ必ず」実行されるケース

例外が発生しない場合のfinallyの挙動

まずは、何も問題が起きない、最も素直なケースを確認します。

Python
def no_exception():
    try:
        print("try: 通常処理を実行します")
        x = 10 + 20
        print(f"計算結果: {x}")
    except Exception:
        print("except: これは呼ばれません")
    else:
        print("else: 例外が無かったので実行されました")
    finally:
        print("finally: 後始末処理を実行します")

    print("関数の末尾です")


if __name__ == "__main__":
    no_exception()
実行結果
try: 通常処理を実行します
計算結果: 30
else: 例外が無かったので実行されました
finally: 後始末処理を実行します
関数の末尾です

このように、例外がまったく起きない場合でもfinallyは必ず呼び出されます。

「後始末を例外有無に関わらず実行したい」というニーズを、シンプルに満たしていることが分かります。

例外が発生してもfinallyが実行される流れ

次に、例外が発生したケースでもfinallyが実行されることを確認します。

Python
def with_exception():
    try:
        print("try: これから 1 / 0 を計算します")
        x = 1 / 0  # ZeroDivisionError が発生
        print("この行は実行されません")
    except ZeroDivisionError as e:
        print(f"except: 例外を捕捉しました: {e}")
    finally:
        print("finally: 例外の有無に関係なく実行されます")

    print("関数の末尾です")


if __name__ == "__main__":
    with_exception()
実行結果
try: これから 1 / 0 を計算します
except: 例外を捕捉しました: division by zero
finally: 例外の有無に関係なく実行されます
関数の末尾です

ここで重要なのは、「例外が発生しても、捕捉されてもしなくても、finally は実行される」という事実です

未捕捉の例外についても確認してみます。

Python
def propagate_exception():
    try:
        print("try: 1 / 0 を実行します")
        x = 1 / 0
    finally:
        # except はないが finally は必ず実行される
        print("finally: 例外が呼び出し元へ伝播する前に実行されます")


if __name__ == "__main__":
    propagate_exception()
    print("この行は実行されません")
実行結果
try: 1 / 0 を実行します
finally: 例外が呼び出し元へ伝播する前に実行されます
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero

未処理の例外が発生してプログラムがエラー終了する場合でも、エラーを投げて関数から抜ける「その直前」にfinallyが実行されていることが分かります。

return・break・continueとfinallyの関係

Pythonのfinallyは、returnbreakcontinueによってブロックを抜ける場合でも必ず実行されます

これは直感に反していることも多いので、コードで確認しておくと安心です。

return と finally

Python
def return_in_try():
    try:
        print("try: return を実行します")
        return "result from try"
    finally:
        print("finally: return の前に必ず実行されます")


if __name__ == "__main__":
    result = return_in_try()
    print("戻り値:", result)
実行結果
try: return を実行します
finally: return の前に必ず実行されます
戻り値: result from try

いったんreturnが決定しても、その直前にfinallyが必ず実行され、その後で関数を抜けることが確認できます。

なお、finally側でreturnを書いた場合は話が変わります。

finally内のreturnは、もともと決まっていた戻り値を上書きしてしまうため、一般に避けるべきパターンです。

Python
def return_in_finally():
    try:
        print("try: 1 を返そうとします")
        return 1
    finally:
        print("finally: 2 を返します")
        return 2  # try の return を上書きする


if __name__ == "__main__":
    print("戻り値:", return_in_finally())
実行結果
try: 1 を返そうとします
finally: 2 を返します
戻り値: 2

break / continue と finally

ループ内でも同様です。

Python
def loop_with_break():
    for i in range(3):
        try:
            print(f"ループ {i}: try 内の処理")
            if i == 1:
                print("ループ 1 で break します")
                break
        finally:
            print(f"ループ {i}: finally 内の処理")

    print("ループの外に出ました")


if __name__ == "__main__":
    loop_with_break()
実行結果
ループ 0: try 内の処理
ループ 0: finally 内の処理
ループ 1: try 内の処理
ループ 1 で break します
ループ 1: finally 内の処理
ループの外に出ました

break によってループを抜ける場合でも、対象となるtryブロックに対応するfinallyは必ず実行されることがわかります。

continueでも同様に、finallyを経由してから次の反復に進みます。

finallyが実行されない「例外的なケース」

ここまでは「正常に制御が進む限り」finally が必ず実行されることを見てきました。

次に、「制御そのものが強制的に途切れる」ようなケースについて整理します。

os._exitやプロセス強制終了でfinallyが動かない場合

Python プロセスそのものが OS レベルで強制終了される場合、finallyには到達しません

代表的なのがos._exit()です。

Python
import os

def use_os_exit():
    try:
        print("try: os._exit(0) を呼び出します")
        os._exit(0)  # 即座にプロセス終了。以下は実行されない
        print("try: この行は実行されません")
    finally:
        # ここは一切実行されない
        print("finally: これは実行されません")


if __name__ == "__main__":
    use_os_exit()
    print("この行も実行されません")

os._exit()は、Python のクリーンアップ処理(例: finally、__del__atexit登録関数など)をすべてスキップして、即座にプロセスを終了します。

同様に、外部からSIGKILLでプロセスを強制終了された場合も、Python に処理を戻さずプロセスが止まるため、finallyは実行されません。

sys.exitとKeyboardInterrupt周りの挙動

sys.exit()は「プロセスを終了する関数」と説明されることが多いですが、その実態はSystemExit例外を送出する関数です

よって、try/finallyにかかっている限り、finallyは通常どおり実行されます。

Python
import sys

def use_sys_exit():
    try:
        print("try: sys.exit(0) を呼び出します")
        sys.exit(0)
        print("try: この行は実行されません")
    finally:
        print("finally: sys.exit の前に実行されます")


if __name__ == "__main__":
    use_sys_exit()
    print("この行は実行されません")
実行結果
try: sys.exit(0) を呼び出します
finally: sys.exit の前に実行されます

ここではfinallyが実行されています。

SystemExit は通常の例外と同様に例外伝播の経路を通るため、その途中にある finally がすべて実行されるためです。

同様に、ユーザーがターミナルでCtrl+Cを押したときに発生するKeyboardInterrupt例外も、基本的には同じ扱いです。

try/finallyで囲われていれば、その finally は実行されます。

Python
import time

def interrupt_example():
    try:
        print("5秒待機します。途中で Ctrl+C を押してみてください")
        time.sleep(5)
        print("待機が終わりました")
    finally:
        print("finally: KeyboardInterrupt が起きてもここは実行されます")


if __name__ == "__main__":
    interrupt_example()

実行中に Ctrl+C を押すと、次のような出力になります(環境により多少変わります)。

5秒待機します。途中で Ctrl+C を押してみてください
^Cfinally: KeyboardInterrupt が起きてもここは実行されます
Traceback (most recent call last):
  ...
KeyboardInterrupt

KeyboardInterrupt も例外として扱われるため、finally に到達できる限り、やはり実行されることが分かります。

無限ループ・デッドロックでfinallyまで到達しないケース

ここまでの例は「try ブロックを何とか抜ける」ことが前提でした。

しかし、そもそも try ブロックの実行が終わらない場合、finally に到達しません

典型的には次のようなパターンがあります。

  • 無限ループで抜け出せない。
  • スレッド間でデッドロックが起きて永遠に待ち続ける。
  • 無限再帰で実質的に戻ってこない(最終的には別の例外になることが多いですが)。

簡単な無限ループの例で確認します。

Python
def infinite_loop():
    try:
        print("無限ループを開始します (強制終了するまで抜けません)")
        while True:
            pass  # 何もしない無限ループ
    finally:
        # ここは到達しない
        print("finally: ここには制御が戻ってきません")


if __name__ == "__main__":
    infinite_loop()

このプログラムは、外部から強制終了するまで走り続けるため、finallyには到達しません。

finally が実行されるためには、少なくとも「try ブロックの実行が終わる」か「例外として中断される」必要があるという点を押さえておくとよいです。

電源断・OSクラッシュなど外的要因による未実行パターン

最後に、物理的・外的な要因でプロセスが止まるケースも整理しておきます。

  • マシンの電源断。
  • OS のカーネルパニックやクラッシュ。
  • 仮想マシンやコンテナの強制停止(電源断相当)。
  • 実行中プロセスへのSIGKILL送信(Unix系)。

これらの場合、Python インタプリタに制御が戻ること自体がありません

したがって、いかに丁寧にfinallyを書いていても実行されません。

これは言い換えると、「finally は Python の制御フローの中ではほぼ必ず実行されるが、OS やハードウェアレベルの障害には無力」ということです。

重要なデータを確実に残したい場合は、トランザクションやジャーナリング、定期フラッシュなど、より堅牢なレイヤーの仕組みを併用する必要があります。

finallyの安全な使い方とベストプラクティス

with文(context manager)とfinallyの使い分け

ファイルオープンやロック取得などの典型的な後始末は、手書きのtry/finallyよりもwith文(context manager)を使うのが、Python では一般的なベストプラクティスです。

with文は内部的にtry/finallyを使っており、リソースの取得と解放を自動的に行ってくれます。

Python
# try/finally を手書きした場合
def read_file_manual(path):
    f = None
    try:
        f = open(path, encoding="utf-8")
        return f.read()
    finally:
        if f is not None:
            f.close()

# with 文を使った場合
def read_file_with(path):
    # open(...) はコンテキストマネージャなので with が使える
    with open(path, encoding="utf-8") as f:
        return f.read()

前者はfinallyclose()を明示的に書いていますが、後者はwith文が裏で同じこと(あるいはそれ以上のこと)をしてくれます。

原則として、単純なリソースの解放にはwith文を優先し、どうしても対応するコンテキストマネージャが用意できない特殊ケースでfinallyを使う、という方針が安全です。

クリーンアップ処理をfinallyに書く際の注意点

finally に書く処理そのものにも注意が必要です。

代表的なポイントを整理します。

  1. finally の中で新たな例外を発生させないようにする
    finally 内で別の例外が発生すると、元々発生していた例外や return などの情報が失われてしまうケースがあります。どうしても例外の可能性がある処理を書く場合は、さらに小さなtry/exceptで包んでログだけ出すなど、慎重な設計が必要です
Python
def cleanup_safely():
    resource = acquire_resource()
    try:
        use_resource(resource)
    finally:
        try:
            resource.close()
        except Exception as e:
            # クリーンアップ失敗はログだけに留めるなど
            print(f"クリーンアップ中にエラー: {e}")
  1. finally に重い処理を入れすぎない

finally は関数の「出口」に必ず挟まるため、ここが重すぎるとレスポンス全体に影響します。

本当に「必ず今すぐ実行しなければならない」処理だけを置き、その他は別スレッドやキューに渡すなど分離した方がよいこともあります。

  1. finally から returnraise を多用しない
    先ほど見たように、finally内のreturnは元の戻り値を上書きします。

予期せぬバグの温床になるため、基本的には避けるのがよいです。

同様に、finally から別の例外を新たにraiseするのも、元の問題を隠してしまうことがあります。

  1. finally の中でグローバル状態を壊さないようにする
    ロックの解放や一時ファイル削除などは必要ですが、その際に誤って他の処理が依存している状態まで壊してしまわないよう、対象を局所化することが重要です。

シグナルや強制終了を考慮した設計のポイント

ここまで見てきたように、OS レベルの強制終了や電源断などに対して、finally だけで完全に安全を保証することはできません

そのため、「どのレイヤーまで何を保証するのか」を意識した設計が大切になります。

いくつかのポイントを挙げます。

  1. シグナル(SIGTERM など)に対するハンドラを用意する

サーバープロセスなどでは、SIGTERMを受け取ったときに「優雅なシャットダウン」を行うことが多いです。

Python ではsignalモジュールでシグナルハンドラを登録し、そこで「終了フラグ」を立て、メインループ側がそのフラグをチェックして順序立てたクリーンアップに進む、といった設計がよく使われます。

Python
import signal
import time

shutdown_flag = False

def handle_sigterm(signum, frame):
    global shutdown_flag
    print("SIGTERM を受信しました。安全に終了します。")
    shutdown_flag = True

signal.signal(signal.SIGTERM, handle_sigterm)

def main_loop():
    try:
        while not shutdown_flag:
            print("作業中...")
            time.sleep(1)
    finally:
        print("finally: 後始末処理を実行します")

if __name__ == "__main__":
    main_loop()

このようにすると、SIGTERMを受け取っても、finallyまで到達できる可能性が高まります。

  1. 状態をこまめに永続化し、「途中で止まっても再開できる」ようにする

電源断などに対しては、アプリケーションコードだけで完全防御することはできません。

そのため、処理の途中経過をトランザクションログや一時テーブルに記録し、再起動時に「どこまで終わっていたか」を確認して再実行できる設計が重要になります。

finally は「できる範囲でのクリーンアップ」を行い、完全な信頼性はストレージやプロトコルの設計に委ねる、という役割分担です。

  1. os._exit や SIGKILL の乱用を避ける

どうしても即座に止めたい場面以外では、os._exitSIGKILLを多用しないことも大切です。

通常はsys.exitSIGTERMなどで「Python に制御を返し、finally などを実行させる」ほうが安全です。

まとめ

Python のfinallyは、「Python の制御フローの中にいる限り、ほぼ必ず最後に実行される」後始末用のブロックです。

例外の有無にかかわらず、またreturnbreakによって抜ける場合でも、必ず通過します。

一方で、OS レベルの強制終了や電源断、os._exitの利用、無限ループやデッドロックなど「制御が戻らない」状況では実行されないことも押さえておく必要があります。

実務では、リソース解放にはwith文を優先し、どうしても必要な場合に限定してfinallyを用いながら、シグナル処理や再開可能な設計と組み合わせて信頼性を高めていくことが重要です。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!