Python講座 / try・except・else・finally・raiseで例外を扱う

例外処理は、回復したり呼び出し元へ報告したりすべき失敗を区別する仕組みです。tryの範囲を絞り、対処できる例外だけを捕捉すると、無関係な不具合を隠さずに済みます。

実行経路の違いを理解する

try内で例外が起きると、一致するexceptへ制御が移ります。tryが例外なく終わるとelseが動き、finallyは通常終了・例外終了をまたぐ後片付けに使います。finally内のreturnは保留中の例外や戻り値を抑えてしまうことがあるため、通常は避けます。

規則への違反はraiseで明示できます。ユーザーが修正できる入力エラーと、上位へ報告すべき保存失敗は同じではありません。どの層で処理を続けられるかを判断します。

失敗の記録と片付けは、デコレーターとコンテキストマネージャーで扱う規則とつながります。

正常値と不正な金額を比較する

exceptions_demo.pyへ全体コードを保存し、仮想環境でpython exceptions_demo.pyを実行します。

def parse_amount(text):
    amount = int(text)
    if amount < 0:
        raise ValueError("amount must be nonnegative")
    return amount

for text in ["8000", "abc", "-1"]:
    try:
        amount = parse_amount(text)
    except ValueError as error:
        print("invalid:", text)
    else:
        print("accepted:", amount)
    finally:
        print("checked")

予想結果です。受け付けた値と、変換失敗・負数を区別し、どの経路でもcheckedが出ることを確認します。

accepted: 8000
checked
invalid: abc
checked
invalid: -1
checked

原因に合う境界で例外を扱う

整数への変換はValueError、ファイル操作はFileNotFoundErrorやPermissionErrorなどを発生させます。except Exception: passで全部を隠すと、不正な状態のまま後続処理が進む場合があります。予想外のエラーには有用な文脈を加えて再送出し、黙って続けないようにします。

リソースには可能ならwithなどのコンテキストマネージャーを使います。finallyで自分で閉じる場合、そもそも初期化に失敗した経路も考えます。上位の例外へ変換しながら原因を残すには、raise NewError(...) from errorという形を使えます。

回復と後片付けを両方試す

入力候補へ"0"を追加して有効であること、空文字を追加するとintの変換で失敗することを確認します。数値でない入力と負数では、原因が違うことも読み取ってください。

例外メッセージには認証情報や個人情報が含まれる場合があります。利用者へは次の行動が分かる説明を示し、診断記録は必要な範囲に限ります。loggingとデバッグで文脈を残す方法を学べますが、すべての詳細が公開出力に適するわけではありません。

例外を捕まえてreturnすると、呼び出し元は戻り値を受け取って続行します。except内の単独のraiseは元の例外を再送出します。記録して続行することが本当の回復なのか、失敗を隠すことなのかを区別します。finally内のreturnは例外や戻り値を隠す場合があるため避けます。

関連する公式リファレンス