採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

L08 ch03 — raise で例外を投げる

テクノロジ系 / L08 exception — 例外処理

L08 ch03 — raise で例外を投げる

「呼び出し元に異常を伝える」ための Python の作法

ゴール

  • raise で意図的に例外を発生させられる
  • 状況に合う組み込み例外型 (ValueError / TypeError / LookupError 等) を選べる
  • 例外チェイン (raise ... from e) で原因を残せる

なぜ raise が要るのか

  • 関数は「成功した値」 だけを返す責務にしたい
  • 異常時に None-1 を返すと、呼び出し元が分岐祭りになる
  • raise で「ここから先は通常フローではない」 と宣言する
# ❌ 戻り値で異常を表現
def divide(a, b):
    if b == 0:
        return None  # 呼び出し元が毎回 None チェック
    return a / b

最小例: raise

def divide(a: int, b: int) -> float:
    if b == 0:
        raise ValueError("b must not be 0")
    return a / b

print(divide(10, 2))   # → 5.0
print(divide(10, 0))   # → ValueError: b must not be 0

→ 関数のシグネチャは「成功時の戻り値型」 だけで済む

組み込み例外の選び方

状況例外型
値の中身がおかしい (範囲外・形式違反)ValueError
型がおかしい (str を期待したのに int)TypeError
key / index が見つからないKeyError / IndexError
機能未実装 (継承先で実装させる)NotImplementedError
業務固有のドメインエラーカスタム例外 (ch02)

「合う型がないなら自作する」 が原則Exception を直接投げない。

バリデーション例

def create_user(name: str, age: int) -> dict:
    if not isinstance(name, str):
        raise TypeError(f"name must be str, got {type(name).__name__}")
    if not name:
        raise ValueError("name must not be empty")
    if age < 0:
        raise ValueError(f"age must be >= 0, got {age}")
    return {"name": name, "age": age}

→ 「最初に弾く (early return / early raise)」 が定石

再 raise (握りつぶさない)

def load_config(path: str) -> dict:
    try:
        with open(path) as f:
            return json.load(f)
    except FileNotFoundError:
        log.warning(f"config not found: {path}")
        raise            # ← そのまま投げ直す

→ ログだけ仕込んで、判断は呼び出し元に委ねる

例外チェイン: raise ... from e

def fetch_user(user_id: int) -> User:
    try:
        row = db.query("SELECT * FROM users WHERE id=?", user_id)
    except DBError as e:
        raise UserNotFoundError(f"id={user_id}") from e
    return User(**row)

→ traceback に「直接の原因」 と「ラップした例外」 の両方が出る

→ 上位層は UserNotFoundError でキャッチ、デバッグ時は元の DBError も追える

FastAPI での raise

from fastapi import HTTPException

@app.get("/items/{item_id}")
def read_item(item_id: int):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="item not found")
    return items[item_id]

HTTPException は FastAPI が捕まえて HTTP レスポンスに変換する

→ 自前の try/except は不要

アンチパターン

# ❌ 文字列を raise (Python 3 では不可・SyntaxError)
raise "error!"

# ❌ Exception を直接 raise (型で絞れない)
raise Exception("something wrong")

# ❌ assert で本番ロジックを守る
assert age >= 0   # python -O で消える

raise ValueError(...) のように 具体型 + 説明文 で投げる

drill

drill/validate_age 関数を raise で実装する

拡張課題

  1. カスタム例外と組み合わせ: class InvalidAgeError(ValueError) を作り、validate_age から投げる
  2. チェイン: int(s)ValueError を捕まえて、InvalidAgeError("not a number") from e に変換する
  3. FastAPI 化: 上記を @app.post("/users") の中で使い、404 ではなく 400 (Bad Request) を返す

よくある詰まり

症状原因対処
raise してもプログラムが止まらない上位で except Exception: pass してるアンチパターン (ch01参照)。型を絞ってログ付きで再 raise
例外メッセージが消えるraise NewError() だけで元を捨てたraise NewError() from e でチェイン
TypeErrorValueError の使い分け「型が違う」 vs 「値が変」型違反は TypeError、値が範囲外/形式違反は ValueError

次に学ぶ領域

領域続けて読む
カスタム例外exception/ch02-custom (予定)
FastAPI のエラーハンドリングfastapi-basic/ch0X-http-exception
ロギング (例外と一緒に出す)標準ライブラリ logging
力試ししたい方は クイズ形式の演習(任意) もあります。読むだけでも十分です。