実行中にエラーが起きても起きなくても、最後に必ずやっておきたい後始末(例: ファイルを閉じる、ロックを解放する)があります。
Pythonではfinally
を使うことで、この後始末を<strong>確実</strong>に実行できます。
本記事では初心者向けに、基本の書き方、実行順序、注意点、実例、そしてwith
構文との使い分けまで丁寧に解説します。
finallyとは(Pythonのtry-except-finallyの基本)
finallyの役割: エラーの有無にかかわらず必ず実行
try-except-finally
は、エラー処理と後始末を分けて書ける構文です。
finally
はエラーの有無にかかわらず必ず実行されるブロックで、典型的にはファイルを閉じる、データベース接続をクローズする、ロックを解放するなどのリソース解放に用います。
実行途中でreturn
やraise
があってもfinally
は実行されるため、後始末を確実にできます。
実行順序(try → except → else → finally)
実行の流れは次のようになります。
最後に必ずfinallyが来る点が重要です。
- まず
try
の中を実行 - 例外が発生したら対応する
except
へ - 例外が発生しなかった場合のみ
else
へ - 最後に
finally
を必ず実行
以下のコードで順序を確認できます。
# 実行順序を出力して確認するサンプル
def flow(case: str) -> None:
try:
print("try: 開始")
if case == "err":
int("not-a-number") # ValueErrorを発生させる
else:
print("try: 本処理を実行")
except ValueError:
print("except: ValueErrorを捕捉")
else:
print("else: 例外なしのときだけ実行")
finally:
print("finally: 後始末を必ず実行")
print("関数末尾\n")
print("=== 正常系 ===")
flow("ok")
print("=== 異常系 ===")
flow("err")
=== 正常系 ===
try: 開始
try: 本処理を実行
else: 例外なしのときだけ実行
finally: 後始末を必ず実行
関数末尾
=== 異常系 ===
try: 開始
except: ValueErrorを捕捉
finally: 後始末を必ず実行
関数末尾
try-finallyだけで使うケース
except
で例外を処理せず、finally
だけで後始末するパターンがあります。
この場合、後始末だけは必ず行い、その後の例外処理は外側に任せたいときに有効です。
# 例: 一時ファイルを確実に削除したいが、エラー処理自体は呼び出し側に任せる
import os
import tempfile
def do_something_with_temp() -> None:
path = None
try:
tmp = tempfile.NamedTemporaryFile(delete=False) # 自動削除を無効化
path = tmp.name
print(f"作成: {path}")
tmp.write(b"hello")
tmp.close()
# ここで何かの処理... 例として故意にエラーを発生
raise RuntimeError("途中で失敗しました")
finally:
# 例外の有無にかかわらず必ず削除する
if path and os.path.exists(path):
os.remove(path)
print(f"削除: {path}")
try:
do_something_with_temp()
except RuntimeError as e:
print("外側で例外を捕捉:", e)
作成: C:\Users\User\AppData\Local\Temp\tmpu37quewp
削除: C:\Users\User\AppData\Local\Temp\tmpu37quewp
外側で例外を捕捉: 途中で失敗しました
elseとの違い(参考)
else
は例外が発生しなかったときだけ実行されます。
対してfinally
は常に実行されます。
成功時のみ行いたい後処理(例: 成功メッセージの表示やコミット)はelse
、成功・失敗に関わらず行いたい後始末(例: クローズ、ロック解放)はfinally
に置くのが原則です。
finallyの書き方と基本パターン
最小例(Pythonのfinallyの使い方)
最小の例では、try
で処理し、必要ならexcept
で例外を処理し、最後に必ずfinally
で後始末します。
def safe_div(a: float, b: float) -> float | None:
try:
print("割り算を実行します")
result = a / b
return result
except ZeroDivisionError:
print("0で割ることはできません")
return None
finally:
# ここは必ず実行されます
print("finally: 後始末(ここでは表示のみ)")
print("結果:", safe_div(10, 2))
print("結果:", safe_div(10, 0))
割り算を実行します
finally: 後始末(ここでは表示のみ)
結果: 5.0
割り算を実行します
0で割ることはできません
finally: 後始末(ここでは表示のみ)
結果: None
エラーあり/なしの動きの違い
上の例のように、成功時はexcept
を通らずにreturn
しますが、それでも必ずfinallyが実行されます。
失敗時はexcept
でエラー対応をした後、やはりfinally
が実行されます。
この「成功・失敗に依存せず最後に1回だけ」という性質のおかげで、後始末の重複や書き漏れを防げます。
returnやraiseがあってもfinallyは実行
return
やraise
が途中にあってもfinallyは実行されます。
def with_return() -> int:
try:
print("tryでreturnします")
return 1
finally:
print("returnの前でもfinallyは実行されます")
def with_raise() -> None:
try:
print("tryで例外を送出します")
raise ValueError("エラー発生")
finally:
print("raiseの前後でもfinallyは実行されます")
print("with_returnの戻り値:", with_return())
try:
with_raise()
except ValueError as e:
print("外側で捕捉:", e)
tryでreturnします
returnの前でもfinallyは実行されます
with_returnの戻り値: 1
tryで例外を送出します
raiseの前後でもfinallyは実行されます
外側で捕捉: エラー発生
finally内の例外に注意
finally内で新たに例外を送出すると、元の例外が上書きされます。
デバッグが難しくなるため、finallyでは極力例外を出さない設計にしましょう。
# finally内の例外が元の例外を隠してしまう例
try:
try:
1 / 0 # ZeroDivisionError
finally:
raise KeyError("finallyで発生した別のエラー")
except Exception as e:
print("最終的に表に出る例外:", type(e).__name__, e)
最終的に表に出る例外: KeyError 'finallyで発生した別のエラー'
また、finallyでreturnすると、元のreturnや例外さえ上書きします。
避けましょう。
def bad_return() -> int:
try:
return 1
finally:
# これは「1」を上書きしてしまう
return 2
print("戻り値:", bad_return())
戻り値: 2
後始末とリソース解放の実例
ファイルを閉じる(open/close)
ファイル操作では確実にcloseすることが重要です。
ここではあえてwith
を使わず、try-except-finally
でクローズします(後述のとおり実務ではwith
推奨)。
# ファイルを作成 -> 読み込み -> finallyでclose
def read_first_line(path: str) -> None:
# デモ用にファイルを用意
with open(path, "w", encoding="utf-8") as wf:
wf.write("1行目\n2行目\n")
f = None
try:
f = open(path, "r", encoding="utf-8")
first = f.readline().rstrip("\n")
print("読み込んだ1行目:", first)
except FileNotFoundError:
print("ファイルが見つかりません")
finally:
if f is not None:
f.close()
print("ファイルをcloseしました")
read_first_line("sample.txt")
読み込んだ1行目: 1行目
ファイルをcloseしました
一時ファイルを削除する
一時ファイルは確実に削除したい代表例です。
import os
import tempfile
def do_with_tmp() -> None:
fd = None
path = None
try:
fd, path = tempfile.mkstemp() # 低レベルAPI: ファイル記述子とパスを返す
print("作成:", path, "存在:", os.path.exists(path))
os.write(fd, b"temp data")
# ここで処理...
finally:
if fd is not None:
os.close(fd) # まずクローズ
if path and os.path.exists(path):
os.remove(path) # その後削除
print("削除:", path, "存在:", os.path.exists(path))
do_with_tmp()
作成: /tmp/tmpxyz456 存在: True
削除: /tmp/tmpxyz456 存在: False
データベース接続をクローズ/ロールバック
成功時はコミット、失敗時はロールバック、最後にクローズという流れを明確にできます。
import sqlite3
def db_transaction(success: bool) -> None:
conn = sqlite3.connect(":memory:") # デモではメモリDB
try:
cur = conn.cursor()
cur.execute("CREATE TABLE t (id INTEGER)")
cur.execute("INSERT INTO t VALUES (1)")
if success:
conn.commit()
print("コミット完了")
else:
# 敢えてエラーを発生させる
raise RuntimeError("DB処理に失敗")
except Exception as e:
print("例外発生:", e)
conn.rollback()
print("ロールバック完了")
finally:
conn.close()
print("コネクションをcloseしました")
print("--- 成功時 ---")
db_transaction(True)
print("--- 失敗時 ---")
db_transaction(False)
--- 成功時 ---
コミット完了
コネクションをcloseしました
--- 失敗時 ---
例外発生: DB処理に失敗
ロールバック完了
コネクションをcloseしました
ロックを確実に解放する
ロックは解放漏れがあるとデッドロックの原因になります。
必ずfinallyで解放しましょう。
import threading
def use_lock(cause_error: bool) -> None:
lock = threading.Lock()
lock.acquire()
try:
print("ロック獲得中:", lock.locked())
if cause_error:
raise RuntimeError("クリティカルセクションで失敗")
print("クリティカルセクションの処理")
finally:
if lock.locked():
lock.release()
print("ロックを解放しました")
print("ロック状態(解放後):", lock.locked())
print("--- 正常 ---")
use_lock(False)
print("--- 例外発生 ---")
use_lock(True)
--- 正常 ---
ロック獲得中: True
クリティカルセクションの処理
ロックを解放しました
ロック状態(解放後): False
--- 例外発生 ---
ロック獲得中: True
ロックを解放しました
ロック状態(解放後): False
ネットワーク接続をクローズする
ソケットも必ずcloseが必要です。
ネットワークの成否に関わらずfinally
でクローズします。
import socket
def fetch_head_example() -> None:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2)
try:
# example.com:80 へ接続を試みる
result = s.connect_ex(("example.com", 80))
if result == 0:
print("接続成功")
s.sendall(b"HEAD / HTTP/1.0\r\nHost: example.com\r\n\r\n")
head = s.recv(64)
print("1行目:", head.splitlines()[0].decode("ascii", "ignore"))
else:
print("接続失敗:", result)
finally:
s.close()
print("ソケットをcloseしました")
fetch_head_example()
接続成功
1行目: HTTP/1.0 200 OK
ソケットをcloseしました
(ネットワーク環境によっては「接続失敗: <エラーコード>」になることがあります。いずれの場合も最後のクローズは必ず行われます。)
with構文との使い分け
まずwith構文を優先(ファイル操作など)
リソースの多くは「コンテキストマネージャ」として実装されており、with構文で自動的に後始末できます。
可能なときはwith
を優先しましょう。
# withなら自動でcloseされる
path = "with_sample.txt"
with open(path, "w", encoding="utf-8") as w:
w.write("hello with\n")
with open(path, "r", encoding="utf-8") as f:
line = f.readline().strip()
print("読み込み:", line)
# ここを出た時点でfは自動クローズ済み
print("自動クローズ確認:", f.closed)
読み込み: hello with
自動クローズ確認: True
withが使えない場面でtry-finally
すべての操作がコンテキストマネージャ化されているわけではありません。
例えば「作業ディレクトリを一時的に変更して、必ず元に戻す」といった状態の復元にはtry-finally
が有効です。
import os
import tempfile
def chdir_temporarily() -> None:
original = os.getcwd()
tmpdir = tempfile.mkdtemp()
try:
print("元のcwd:", original)
os.chdir(tmpdir)
print("変更後cwd:", os.getcwd())
# ここでtmpdir内での処理を行う
finally:
# 例外の有無にかかわらず必ず元に戻す
os.chdir(original)
print("復帰cwd:", os.getcwd())
chdir_temporarily()
元のcwd: /path/to/original
変更後cwd: /tmp/tmpabcd12
復帰cwd: /path/to/original
パターン比較(with vs try-except-finally)
以下は使い分けの目安です。
観点 | with構文 | try-except-finally |
---|---|---|
後始末の自動化 | 自動 | 手動で記述が必要 |
例外発生時のクリーンアップ | 自動で安全に実行 | 書き方次第で安全に実行 |
可読性 | 高い(ボイラープレートが少ない) | やや冗長になりがち |
複数リソースの管理 | withのネスト/併記で簡潔 | tryのネストが深くなりがち |
状態の復元(os.chdirなど) | 適用困難なケースあり | 柔軟に記述可能 |
基本はwith、withにできない場面をtry-finallyで補う、という発想が安全です。
初心者向けベストプラクティス
初心者の方は、次を意識すると安全で読みやすいコードになります。
- with構文を最優先し、対応していない場合のみ
try-finally
を使う。 - finallyでは例外を起こさない・returnしない(元の情報を上書きするため)。
finally
は短く・単純に(クローズやフラグ解除など最小限の後始末だけを書く)。- 成功時だけ行う処理(例: コミット、成功ログ)は
else
に置く。 - 例外を握りつぶさない。必要なら外側へ伝播させ、
finally
で後始末だけ行う。
まとめ
finallyは成功・失敗に関わらず必ず実行されるため、後始末やリソース解放を確実に行う最重要の仕組みです。
実行順序はtry → except → else → finally
で、return
やraise
があってもfinally
は実行されます。
一方でfinally内の例外やreturnは元の情報を上書きするため避けるべきです。
実務ではまずwith構文を優先し、withで表現できない状態復元や特殊な後始末のみtry-finally
で補うのが安全な使い分けです。
これらの原則を押さえれば、ファイル、DB、ロック、ネットワークといった多様なリソースを堅牢かつ読みやすく扱えるようになります。