【Python】例外処理(try/except)の使い方|複数except・else・finally・raise

【Python】tryを使用した例外処理の方法 Python

プログラムは、ファイルが無い・数値に変換できない・ゼロで除算する、といった理由で実行中にエラー(例外)が発生します。何もしないとプログラムはそこで止まってしまいます。これに備えて、エラーが起きても処理を続けたり、適切に対応したりする仕組みが例外処理です。Pythonではtryexceptで書きます。

注意したいのは、except:だけで「すべての例外」を捕まえてしまう書き方です。これは本来気づくべきバグまで握りつぶしてしまうため、避けるべきです。捕まえる例外の種類を指定するのが基本になります。この記事では、実機のPythonで確認しながら、例外処理の使い方を整理します。

先に結論

  • try:に通常処理、except エラー型:にエラー時の処理を書きます。
  • 捕まえる例外は種類を指定します(except ValueError:など)。複数の型はexcept (A, B):とまとめられます。
  • 例外の中身はexcept エラー型 as e:で受け取り、str(e)でメッセージが得られます。
  • else:は例外が起きなかったとき、finally:成功・失敗にかかわらず必ず実行されます。
  • 自分で例外を起こすにはraise ValueError("メッセージ")を使います。
  • except:だけの「全部捕まえ」は避け、必要な例外だけを捕まえます。

例外処理は関数やファイル操作と一緒に使うことが多いので、関数(def)の使い方、データ構造のリスト(list)辞書(dict)もあわせて参考になります。

スポンサーリンク

try/exceptの基本

エラーが起きうる処理をtry:に書き、エラーが起きたときの処理をexcept:に書きます。tryの中でエラーが発生すると、すぐにexceptへ移ります。

basic.py
# 文字列を数値に変換(失敗するとエラー)
try:
    n = int("abc")
    print(n)
except ValueError:
    print("数値に変換できませんでした")

# エラーが起きてもプログラムは止まらず、次に進む
print("処理を続行")

実機でも、int("abc")で発生するValueErrorexcept ValueError:で捕まえられました。例外を捕まえると、プログラムは止まらずにexceptの処理を行い、その後の処理へ進みます。exceptには、捕まえたい例外の種類(ValueErrorなど)を指定するのが基本です。

例外の種類を指定する(複数except)

エラーの種類によって対応を変えたいときは、exceptを複数並べます。「数値変換の失敗」と「ゼロ除算」を別々に処理する、といった使い方です。

multiple-except.py
def divide(s):
    try:
        return 10 / int(s)
    except ValueError:
        return "数値ではありません"
    except ZeroDivisionError:
        return "ゼロで割れません"

divide("abc")   # 数値ではありません
divide("0")     # ゼロで割れません
divide("2")     # 5.0

# 複数の型を1つのブロックで扱うならタプルでまとめる
try:
    risky_operation()
except (ValueError, TypeError):
    print("入力が不正です")

実機でも、divide("abc")ValueErrordivide("0")ZeroDivisionErrorと、種類ごとに別の処理になりました。同じ対応でよい複数の例外は、except (ValueError, TypeError):のようにタプルでまとめられます。

例外オブジェクトを受け取る(as e)

発生した例外の詳細(メッセージや種類)を知りたいときは、except エラー型 as e:と書きます。eに例外オブジェクトが入り、メッセージや型名を取り出せます。

exception-object.py
try:
    [1, 2, 3][10]
except IndexError as e:
    print(str(e))                 # list index out of range
    print(type(e).__name__)       # IndexError

# ログやエラーメッセージの表示に使う
try:
    int("abc")
except ValueError as e:
    print(f"変換に失敗しました: {e}")

実機でも、except IndexError as e:str(e)list index out of rangetype(e).__name__IndexErrorになりました。エラーメッセージをログに残したり、利用者に表示したりするときに役立ちます。

else と finally

tryには、elsefinallyも付けられます。else例外が起きなかったときに実行され、finally成功・失敗にかかわらず必ず実行されます。

else-finally.py
def safe_divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("ゼロ除算エラー")
    else:
        # 例外が起きなかったときだけ実行
        print(f"計算成功: {result}")
    finally:
        # 必ず実行(後始末に使う)
        print("処理を終了します")

safe_divide(10, 2)
# 計算成功: 5.0
# 処理を終了します

safe_divide(10, 0)
# ゼロ除算エラー
# 処理を終了します
finallyは必ず実行される

実機で確認したところ、finallyは例外が起きても起きなくても必ず実行され、さらにtryの中でreturnした場合でも実行されました。ファイルや接続のクローズなど、「何があっても必ず行いたい後始末」finallyに書きます。elseは「例外が起きなかったとき」だけ実行されるので、「tryが成功したときの続きの処理」を書くのに向いています。

raiseで例外を発生させる

入力チェックなどで、自分から例外を起こして処理を止めたいときはraiseを使います。raise エラー型("メッセージ")と書きます。

raise.py
def check_age(age):
    if age < 0:
        raise ValueError(f"年齢が不正です: {age}")
    return age

try:
    check_age(-5)
except ValueError as e:
    print(e)   # 年齢が不正です: -5

# except の中で raise すると、例外を上位へ投げ直せる(再送)
try:
    try:
        int("abc")
    except ValueError:
        print("ログを記録")
        raise            # そのまま上位へ投げ直す
except ValueError:
    print("上位で最終処理")

実機でも、raise ValueError("年齢が不正です: -5")で発生させた例外をexceptで捕まえられました。exceptの中でraise(引数なし)と書くと、今捕まえた例外をそのまま上位へ投げ直す(再送する)ことができます。「ログだけ記録して、エラー自体は上位に任せる」といった場面で使います。

【重要】広すぎるexceptは避ける

やってしまいがちなのが、except:だけ、またはexcept Exception:すべての例外を捕まえる書き方です。一見、安全に見えますが、本来気づくべきバグ(変数名の打ち間違いなど)まで握りつぶしてしまい、原因が分からなくなります。

bare-except.py
# NG: すべてを捕まえて握りつぶす(バグも隠れる)
try:
    result = some_function()
except:
    pass   # 何のエラーか分からず、バグも気づけない

# NG: 範囲が広すぎる
try:
    process()
except Exception:
    print("エラー")   # どんなエラーでも同じ扱い

# OK: 捕まえる例外を具体的に指定する
try:
    value = int(user_input)
except ValueError:
    print("数値を入力してください")
捕まえる例外は具体的に指定する

except:だけの書き方は、KeyboardInterrupt(Ctrl+Cでの中断)など、本来止めるべき割り込みまで捕まえてしまいます。また、想定外のバグも握りつぶすため、不具合の発見が遅れます。「どの例外を、なぜ捕まえるのか」を明確にして、種類を具体的に指定してください。どうしても広く捕まえる必要があるなら、せめてexcept Exception as e:としてeを記録(ログ出力)し、何が起きたか分かるようにします。なお、複数のexceptを書くときは、具体的な型を上、汎用のExceptionを最後に並べます(順番が逆だと、先にExceptionで捕まり、具体的なexceptに届きません)。

よくある例外の種類

Pythonには多くの組み込み例外があります。よく出会うものを知っておくと、exceptで正しく指定できます。

例外 起きる場面
ValueError 値が不適切(int("abc")など)
TypeError 型が合わない(数値と文字列の足し算など)
KeyError 辞書に存在しないキーを指定
IndexError リストの範囲外のインデックス
FileNotFoundError ファイルが見つからない
ZeroDivisionError ゼロで割った
AttributeError 存在しない属性・メソッドを呼んだ

どの例外が起きるか分からないときは、まずexcept Exception as e:type(e).__name__を表示させて型を確認し、それを具体的なexceptに指定する、という進め方が確実です。

よくある失敗

except: だけですべて握りつぶす

本来気づくべきバグまで隠れます。捕まえる例外の種類を具体的に指定します。

具体的な例外より先にExceptionを書く

except Exception:を先に書くと、そこですべて捕まり、後ろの具体的なexceptに届きません。具体的な型を上、汎用を最後に並べます。

finallyで後始末を書き忘れる

ファイルや接続は、エラーが起きても閉じる必要があります。finally(またはwith文)で確実に後始末します。

例外を握りつぶしてログも残さない

except: passでは原因が分かりません。最低でもexcept Exception as e:eをログに残します。

try の範囲を広げすぎる

tryに多くの処理を入れると、どこでエラーが起きたか分かりにくくなります。エラーが起きうる箇所に絞って囲みます。

よくある質問

Qexceptには何を指定すればいいですか?
A捕まえたい例外の種類を指定します(except ValueError:など)。except:だけだとすべての例外を捕まえ、バグも隠してしまうため避けます。どの例外か分からないときは、まずexcept Exception as e:type(e).__name__を表示して型を調べます。
Qelseとfinallyの違いは?
Aelse例外が起きなかったときだけ実行され、try成功時の続きの処理に使います。finally成功・失敗にかかわらず必ず実行され、ファイルのクローズなどの後始末に使います。try内でreturnしてもfinallyは実行されます。
Q複数の例外を同じ処理で捕まえるには?
Aexcept (ValueError, TypeError):のように、型をタプルでまとめます。別々の処理にしたいときは、exceptを複数並べ、具体的な型を上、汎用のExceptionを最後に書きます。
Q自分でエラーを発生させるには?
Araise ValueError("メッセージ")を使います。入力チェックで不正な値を見つけたときなどに、処理を止めて呼び出し元に知らせます。exceptの中で引数なしのraiseを書くと、捕まえた例外を上位へ投げ直せます。
Q例外のメッセージを取り出すには?
Aexcept エラー型 as e:と書き、str(e)でメッセージ、type(e).__name__で例外の型名が得られます。ログ出力や利用者へのエラー表示に使います。

まとめ

  • try:に通常処理、except エラー型:にエラー時の処理を書きます。
  • 捕まえる例外は種類を具体的に指定します。複数はexcept (A, B):でまとめられます。
  • 例外の中身はexcept エラー型 as e:で受け取り、str(e)で取り出します。
  • elseは成功時、finally必ず実行され、後始末に使います。
  • 自分で起こすならraiseexcept内のraiseで再送できます。
  • except:だけの「全部捕まえ」は避け、具体的な型を上・汎用を最後に並べます。

例外処理は、プログラムを止めずにエラーへ対応するための仕組みです。「捕まえる例外を具体的に指定する」「後始末はfinally」という2点を押さえれば、堅牢で原因の分かりやすいコードを書けるようになります。