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 で実装する
拡張課題
- カスタム例外と組み合わせ:
class InvalidAgeError(ValueError)を作り、validate_ageから投げる - チェイン:
int(s)のValueErrorを捕まえて、InvalidAgeError("not a number") from eに変換する - FastAPI 化: 上記を
@app.post("/users")の中で使い、404 ではなく 400 (Bad Request) を返す
よくある詰まり
| 症状 | 原因 | 対処 |
|---|---|---|
raise してもプログラムが止まらない | 上位で except Exception: pass してる | アンチパターン (ch01参照)。型を絞ってログ付きで再 raise |
| 例外メッセージが消える | raise NewError() だけで元を捨てた | raise NewError() from e でチェイン |
TypeError と ValueError の使い分け | 「型が違う」 vs 「値が変」 | 型違反は TypeError、値が範囲外/形式違反は ValueError |
次に学ぶ領域
| 領域 | 続けて読む |
|---|---|
| カスタム例外 | exception/ch02-custom (予定) |
| FastAPI のエラーハンドリング | fastapi-basic/ch0X-http-exception |
| ロギング (例外と一緒に出す) | 標準ライブラリ logging |
力試ししたい方は クイズ形式の演習(任意) もあります。読むだけでも十分です。