Pythonにおける例外処理の仕組みと実装パターン

代表的な組み込み例外

Pythonには、プログラム実行中に発生しうる様々なエラー状態に対応するための組み込み例外クラスが用意されています。これらを正しく理解することで、デバッグやエラーハンドリングが容易になります。

# リストのインデックスが範囲外の場合
IndexError: list index out of range

# 0による除算を行った場合
ZeroDivisionError: division by zero

# 異なるデータ型間で不適切な演算を行った場合
TypeError: unsupported operand type(s) for +: 'int' and 'str'

# オブジェクトに存在しない属性やメソッドを参照した場合
AttributeError: 'module' object has no attribute 'non_existent_method'

# 指定したファイルが見つからない場合
FileNotFoundError: [Errno 2] No such file or directory: 'data.txt'

# 辞書に存在しないキーを参照した場合
KeyError: 'invalid_key'

try-except構文の基本

Pythonにおける例外処理の基本はtryブロックとexceptブロックで構成されます。try節には例外が発生する可能性のあるコードを記述し、except節にはその例外が発生した際の処理を記述します。

基本的な構造

あらゆる例外をキャッチする基本的な記述方法は以下の通りです。

try:
    # 例外が発生する可能性のある処理
    result = 10 / 0
except:
    # 例外発生時の処理
    print("エラーが発生しました")

特定の例外の捕捉

例外の種類を特定して処理を行うには、exceptの後に例外クラス名を指定します。

try:
    file = open('nonexistent.txt', 'r')
except FileNotFoundError:
    print("ファイルが見つかりません")

複数の例外をまとめて処理

複数の異なる例外に対して同じ処理を行いたい場合は、タプルを使って例外クラスをグループ化できます。

try:
    data = [1, 2, 3]
    idx = int(input("インデックスを入力: "))
    print(data[idx])
except (IndexError, ValueError):
    print("無効な入力またはインデックス範囲外のエラーです")

複数のexceptブロックによる分岐

例外の種類ごとに異なる対応を必要とする場合は、exceptブロックを並べて記述します。これはif-elif文のような挙動を示します。

try:
    numerator = 100
    denominator = int(input("分母を入力: "))
    print(numerator / denominator)
except ZeroDivisionError:
    print("0で割ることはできません")
except ValueError:
    print("数値を入力してください")

例外オブジェクトの取得

asキーワードを使用すると、発生した例外オブジェクトそのものを変数に代入し、エラーメッセージなどの詳細情報を取得できます。

try:
    import nonexistent_module
except ImportError as e:
    print(f"インポートエラーの詳細: {e}")

elseとfinallyブロック

例外処理のフローをより細かく制御するために、elsefinallyを使用できます。

  • else: tryブロック内で例外が発生しなかった場合にのみ実行されます。
  • finally: 例外の発生有無にかかわらず、必ず最後に実行されます。主にファイルのクローズやリソースの解放に使用されます。
try:
    file = open('sample.txt', 'r')
    content = file.read()
except FileNotFoundError:
    print("ファイルが存在しません")
else:
    print(f"読み込み成功: {len(content)}文字")
finally:
    print("処理終了")
    # リソースの解放処理など

ネストされた例外処理

tryブロックを入れ子にすることも可能です。内側のexceptで処理されなかった例外は、外側のtry-exceptへと伝播(バブルアップ)していきます。

try:
    try:
        text = "hello"
        number = int(text)
    except ValueError:
        print("内部ブロック: 値の変換に失敗しました")
except:
    print("外部ブロック: 捕捉されなかった例外を処理します")

上記の例では、内側のint(text)ValueErrorを発生させます。これは内側のexcept ValueErrorで捕捉されるため、「内部ブロック」のメッセージが出力されます。もし内側でIndexErrorを捕捉しようとしていた場合、例外は外側へ伝播し、「外部ブロック」が実行されます。

例外の明示的な発生 (raise)

プログラマの意図的に例外を発生させるにはraiseキーワードを使用します。これにはいくつかの使用方法があります。

現在の例外の再スロー

exceptブロック内でraiseのみを記述すると、捕捉した例外を再度呼び出し元へスローします。

try:
    # 何らかの処理
    pass
except Exception:
    # ロギングなどの処理を行った後、再度例外を発生させる
    raise

新しい例外の発生

特定の例外クラスを指定してスローします。

raise TypeError("型が一致しません")

バリデーションへの応用

raiseは入力値の検証など、ロジック上エラーとみなすべき状況で活用されます。以下は、年齢が正の整数であるかをチェックする例です。

def validate_age(age_str):
    try:
        age = int(age_str)
    except ValueError:
        raise ValueError("年齢は整数で入力してください")
    
    if age < 0:
        raise ValueError("年齢は0以上である必要があります")
    
    return age

try:
    user_input = input("年齢を入力: ")
    valid_age = validate_age(user_input)
    print(f"年齢 {valid_age} が登録されました")
except ValueError as err:
    print(f"入力エラー: {err}")

タグ: Python ExceptionHandling TryExcept Raise RuntimeError

7月18日 01:15 投稿