エラー処理では、正常系だけに実行したい処理と、失敗時に実行したい処理、成功失敗に関係なく片付けたい処理を分けることが重要です。
Pythonのtry-except-else
は「例外が起きなかったときだけ」実行されるelse
を持ち、読みやすく安全なコードに役立ちます。
本記事ではfinally
との違いも含め、初心者向けに丁寧に解説します。
try-except-elseとは(Python)
エラーが発生しなかった場合のみ実行するelse
elseの位置と意味
try-except-else
におけるelse
は、「try
ブロックで例外が起きなかった場合にだけ実行」される特別な節です。
構造としてはexcept
の後、finally
より前に書きます。
これにより、エラーがなかった場合にのみ行いたい「後続の正常処理」を明確に分離できます。
elseを使わない場合との違い
もしelse
を使わない場合、成功時の処理をtry
の中に書きがちで、「どこまでが例外を想定したい範囲か」が不明瞭になります。
例外が出るかもしれない最小限の処理だけをtry
に閉じ込め、成功時の本筋処理はelse
に分けると読みやすくなります。
try-except-elseの基本構文と実行順序
基本構文
もっともよく使う4つの節はtry
、except
、else
、finally
です。
まずは構文を確認します。
# 基本構文の例
try:
# 例外が起こるかもしれない最小限の処理を書く
risky_value = 10 / 2
except ZeroDivisionError as e:
# 特定の例外が起きたときの処理
print("ゼロ除算エラー:", e)
else:
# tryで例外が発生しなかった場合のみ実行される
print("成功しました。結果:", risky_value)
finally:
# 成功・失敗に関わらず必ず実行される
print("片付け処理を実行しました")
実行順序の理解
実行順序は「try → (例外があれば) except → (例外がなければ) else → finally」です。
finallyは常に最後に実行されます。
成功時と失敗時でどの節が動くかが変わる点を押さえましょう。
成功時とエラー時の出力の違い
下の小さなデモで、成功と失敗の両パターンを確認します。
def demo(mode: str) -> None:
print(f"--- mode={mode} ---")
try:
if mode == "ok":
x = 6 / 2 # 成功(例外なし)
else:
x = 6 / 0 # 失敗(ZeroDivisionError)
except ZeroDivisionError:
print("except: ゼロ除算でした")
else:
print(f"else: 成功。x={x}")
finally:
print("finally: 必ず実行されます")
demo("ok")
demo("ng")
実行結果(成功と失敗の2パターン):
--- mode=ok ---
else: 成功。x=3.0
finally: 必ず実行されます
--- mode=ng ---
except: ゼロ除算でした
finally: 必ず実行されます
成功時はelse
が動き、失敗時はexcept
が動き、どちらでもfinally
が動くのがポイントです。
使いどころの考え方(初心者向け)
何をtryに、何をelseに分けるか
try
には「失敗する可能性がある操作(最小限)」、else
には「成功したことを前提に進めたい本筋の処理」を置きます。
例えば、ファイルを開く処理はtry
、開けたファイルの内容で計算する処理はelse
が典型です。
使わない方がよい場面
else
の中で再び例外が多発するような重い処理を書くと理解が難しくなります。
elseは短くシンプルに保ち、必要なら別関数に切り出すと読みやすくなります。
try-except-elseの使い方(サンプル)
計算が成功したときだけメッセージを出す
ゼロ除算の可能性がある割り算を例に、成功時だけ結果を表示するサンプルです。
def safe_div(a: float, b: float) -> None:
"""a / b を試み、成功時だけ結果を表示する"""
try:
result = a / b # ここで ZeroDivisionError の可能性
except ZeroDivisionError as e:
print(f"エラー: ゼロ除算です ({e})")
else:
# 例外がなかったので、ここは成功時だけ通る
print(f"成功: {a} / {b} = {result}")
safe_div(8, 2) # 成功パターン
safe_div(5, 0) # エラーパターン
成功: 8 / 2 = 4.0
エラー: ゼロ除算です (division by zero)
成功時の表示をelse
に移したことで、try
の範囲は「例外が起きそうな最小限」に絞れています。
この分離が読みやすさと保守性につながります。
ファイル読み込みが成功したら処理を続ける
ファイルを開く部分だけをtry
に入れ、開けたときだけ中身を使って計算を行う例です。
デモのためにファイル有無を切り替えられるようにしています。
import os
def read_first_int_and_double(simulate_missing: bool) -> None:
"""ファイルが開けたら先頭行を整数として読み、2倍して表示"""
filename = "example_numbers.txt"
# デモ用: ファイルを用意するかどうかを切り替え
if not simulate_missing:
with open(filename, "w", encoding="utf-8") as f:
f.write("42\nhello\n")
try:
f = open(filename, "r", encoding="utf-8") # FileNotFoundError の可能性
except FileNotFoundError as e:
print(f"エラー: ファイルがありません ({e})")
else:
# ファイルが開けたときだけ続きの処理をする
with f:
first_line = f.readline().strip()
# else の中で起きた別の例外はこの except では捕まらない点に注意
value = int(first_line) # ValueError の可能性
print("2倍の結果:", value * 2)
finally:
# デモの片付け(存在すれば削除)
if os.path.exists(filename):
os.remove(filename)
print("=== 成功ケース ===")
read_first_int_and_double(simulate_missing=False)
print("=== 失敗ケース(ファイルなし) ===")
read_first_int_and_double(simulate_missing=True)
=== 成功ケース ===
2倍の結果: 84
=== 失敗ケース(ファイルなし) ===
エラー: ファイルがありません ([Errno 2] No such file or directory: 'example_numbers.txt')
try
では「開く」だけ、else
では「中身を使う」処理に分けると、責務の境界が明確になります。
else内で新しく起きた例外(ここではValueError
)は、外側のexcept
では捕まりません。
必要ならelse
の中でも個別にtry-except
を使います。
成功時の後処理をelseにまとめる
すべての入力が数値に変換できた場合にだけ、平均を計算してログを出す例です。
def process_numbers(texts: list[str]) -> None:
"""全要素の変換が成功したときだけ平均を計算して報告"""
try:
numbers = [float(t) for t in texts] # どれか1つでも変換失敗なら ValueError
except ValueError as e:
print("変換エラー:", e)
else:
# ここは全要素の変換が成功したときだけ通る
avg = sum(numbers) / len(numbers)
print("平均値:", avg)
print("ログ: 正常に処理が完了しました")
print("=== 成功ケース ===")
process_numbers(["1.5", "2", "3.5"])
print("=== 失敗ケース ===")
process_numbers(["1.5", "NaN?", "3.5"])
=== 成功ケース ===
平均値: 2.3333333333333335
ログ: 正常に処理が完了しました
=== 失敗ケース ===
変換エラー: could not convert string to float: 'NaN?'
「全部変換できた」という前提が必要な処理(平均計算やログ)はelse
に集約すると安全で読みやすくなります。
この分離により、例外時の回復処理と成功時の後処理が衝突しません。
finallyとの違いをやさしく解説
elseは成功時の追加処理
一言まとめ
else
は「例外が起きなかったときだけ走る後続処理」を書く場所です。
成功前提のロジック、成功ログ、成功メトリクスの集計などに適します。
典型例
ファイルや入力の検証が済んだ後にだけ、分析や整形を行うといった使い方が代表例です。
finallyは必ず実行する片付け
一言まとめ
finally
は「成功しても失敗しても必ず実行される片付け処理」を書く場所です。
ファイルを閉じる、ロックを解放する、一時ファイルを削除するなどを記述します。
典型例
外部リソース(ファイル、ソケット、データベース接続)の解放はfinally
に置くのが定石です。
以下の表に違いをまとめます。
節 | 実行される条件 | 主な用途 | 注意点 |
---|---|---|---|
else | tryで例外が発生しなかったときだけ | 成功時の後続処理、ログ、メトリクス | else内での新たな例外は外側のexceptでは捕まらない |
finally | 成否に関わらず常に | リソース解放、ロック解除、後始末 | finallyでreturnすると例外が消えることがある |
「成功時の追加処理はelse
、必ず必要な後始末はfinally
」という役割分担を覚えましょう。
elseとfinallyを組み合わせる例
ファイルを開く成否で処理を分岐しつつ、必ずクローズする例です。
実務ではwith
文が推奨ですが、学習のために明示的にclose()
しています。
def read_all_text(filename: str) -> None:
f = None
try:
f = open(filename, "r", encoding="utf-8") # FileNotFoundError の可能性
except FileNotFoundError:
print("ファイルが見つかりません")
else:
# 開けたときだけ読み込む
content = f.read()
print("読み込み成功。文字数:", len(content))
finally:
# f が開かれていれば必ず閉じる
if f is not None:
f.close()
print("クリーンアップ完了")
# デモ用にファイルを作ってから呼び出す
with open("hello.txt", "w", encoding="utf-8") as tmp:
tmp.write("hello world")
read_all_text("hello.txt")
読み込み成功。文字数: 11
クリーンアップ完了
else
は成功時の本処理、finally
は片付け、と役割を分担すると予期せぬリソースリークを防げます。
よくあるミスと回避策
ミス1: exceptを広く取りすぎる
bare except(例: except:
)はキーボード割り込みなども捕まえてしまい危険です。
回避策はexcept Exception as e:
か、できれば具体的な例外名で限定します。
# 悪い例
try:
do_something()
except:
pass # すべて握りつぶしてしまう
# 良い例
try:
do_something()
except ValueError as e:
handle_value_error(e)
ミス2: tryが広すぎる
多くの行をtry
に含めると、どこが失敗の原因か分かりにくくなります。
回避策は「本当に失敗しうる最小限の行」をtry
に入れ、後続はelse
へ。
# 悪い例: 失敗しない計算まで try に入っている
try:
f = open("data.txt")
data = f.read()
result = complex_transform(data) # ここで失敗しても open 失敗と混同されやすい
print(result)
except Exception as e:
print("何かが失敗:", e)
# 良い例: 開くまでを try、処理は else
try:
f = open("data.txt")
except FileNotFoundError as e:
print("ファイルなし:", e)
else:
with f:
result = complex_transform(f.read())
print(result)
ミス3: finallyでreturnしてしまう
finally内のreturn
は例外を「上書き」して見えなくすることがあります。
回避策はfinallyでは片付けのみにし、戻り値はelse
やtry
側で決めます。
# 悪い例
def bad() -> int:
try:
1 / 0
finally:
return 0 # 例外が消える
# 良い例
def good() -> int:
try:
x = 1 / 1
except ZeroDivisionError:
return -1
else:
return int(x)
finally:
cleanup()
ミス4: elseに重い処理を詰め込みすぎる
else
が長大になると読みづらく、例外の発生源も見失います。
回避策はelseでは「成功の確認後にすぐ呼びたい関数を1〜2個呼ぶ」程度にとどめ、詳細は関数に切り出すことです。
初心者向けベストプラクティス(Python)
exceptで例外を広く取りすぎない
まずは「何が起こりうるか」を特定し、except
は可能な限り具体的な例外で絞り込みます。
無闇に広い捕捉はバグの潜伏やデバッグの難しさにつながります。
どうしても広く取りたい場合は、except Exception as e:
に限定してログを残し、再送出(raise
)を検討しましょう。
elseは短くシンプルに書く
else
には「成功が保証された前提での最小限の処理」だけを書き、長くなりそうなら関数に委譲します。
これにより、tryの責務(失敗しうる最小単位)と、elseの責務(成功後の本筋)がはっきりします。
リソースの解放はfinallyで行う
ファイル、ソケット、ロックなどはfinally
またはwith
(コンテキストマネージャ)で必ず解放します。
例外が発生しても確実に片付けが実行されるため、リソースリークを防げます。
with open(...)
は背後でtry-finally
相当の制御をしてくれる便利な構文です。
まとめ
try-except-else
のelse
は「例外が起きなかったときだけ実行したい処理」を整理する強力な手段です。
成功と失敗の流れを明確に分離でき、コードの見通しが良くなります。
成功時の本処理はelse
、失敗時の補足はexcept
、片付けはfinally
という役割分担を意識してください。
最後にもう一度強調します。
try
は「失敗しうる最小限」、else
は「成功後の本筋」、finally
は「必ずやる片付け」です。
この型を身につければ、エラーに強く読みやすいPythonコードが書けるようになります。