1. try/except の基本
Pythonでは実行時に予期しないエラーが発生すると、何も対処しなければプログラムが強制終了(クラッシュ)します。
例えば int("N/A") を実行すると ValueError: invalid literal for int() with base 10: 'N/A'
というエラーが発生し、以降の処理は一切実行されなくなります。
医療データは欠損値・不正値が混入することが日常的で、
「1件のエラーで処理全体が止まる」事態は許容できません。
try/except を使えばエラーを「捕まえて」適切に処理しながらプログラムを継続できます。
try ブロック内でエラーが発生すると処理が中断し、対応する except ブロックへ移ります。
except で捕捉されなかった例外はそのまま上位に伝播します。
💡 主な例外型: ValueError(不正な値)、TypeError(型の不一致)、KeyError(辞書に存在しないキー)、IndexError(リスト範囲外)、FileNotFoundError(ファイルなし)、ZeroDivisionError(0除算)
💡 try/except の実行フロー:
try ブロックを実行
├─ 例外が発生しなかった → try ブロック全体を実行して次へ進む
└─ 例外が発生した → その行で中断
├─ 対応する except がある → except ブロックを実行して次へ進む
└─ 対応する except がない → 例外が上位に伝播(プログラムが止まる)
2. 複数の例外型を捕捉する
except 例外型 as e: と書くと、例外オブジェクトを変数 e に代入できます。
str(e) でエラーメッセージを、type(e).__name__ で例外クラス名を取得できるため、
エラーの詳細をログに残す・ユーザーにわかりやすく表示する、といった処理に役立ちます。
複数の except ブロックを書くことでエラーの種類ごとに異なる対処ができます。
except は上から順に評価されるため、具体的な例外を先に、
汎用的な Exception を後ろに書くのが基本ルールです。
逆にすると、上位の except Exception がすべてのエラーを吸収してしまい、
下の except KeyError などには一切到達しなくなります。
💡 except の書き方バリエーション:
except ValueError: # 単一の例外型を捕捉 except (ValueError, TypeError): # 複数をまとめて捕捉(どちらでも同じ処理) except ValueError as e: # 例外オブジェクトを e に代入 except Exception as e: # すべての例外を捕捉(最後の手段) except: # 例外型不問(SystemExit も捕捉:非推奨)
except Exception as e: は「想定外のエラーが起きたらログを記録して終了する」
用途で最後のフォールバックとして使います。ただし乱用するとバグを隠す原因になるため注意が必要です。
💡 例外オブジェクト e から取れる情報:
try:
value = int("N/A")
except ValueError as e:
print(str(e)) # → invalid literal for int() with base 10: 'N/A'
print(type(e).__name__) # → ValueError
print(e.args) # → ("invalid literal for int() with base 10: 'N/A'",)
ログファイルにエラー詳細を記録したい場合は str(e) でメッセージ文字列を取得できます。
3. else と finally
else ブロックは、try 内で例外が発生しなかった場合のみ実行されます。
「成功後の処理を try の中に書けばよいのでは?」と思うかもしれません。
しかし、それをすると気づきにくいバグを生む可能性があります。
🐛 try に書きすぎると起きる問題:
# NG 問題のある書き方
try:
value = int(user_input) # ← 入力変換(ここの ValueError を捕まえたい)
save_to_db(value) # ← DB保存(ここの ValueError も同じ except に来てしまう!)
except ValueError:
print("数値を入力してください") # DB保存のバグでも「数値を入力して」と表示されてしまう
save_to_db() の内部でも ValueError が発生すると、
入力は正しかったのに「数値を入力してください」と表示されます。
本当の原因(DBエラー)は隠れてしまいます。
# OK else を使った書き方
try:
value = int(user_input) # ← ここだけを監視
except ValueError:
print("数値を入力してください") # int() のエラーだけを捕捉
else:
save_to_db(value) # try が成功したときだけ実行。ここの ValueError は上の except に吸収されない
else に書いた処理で例外が起きた場合は except をスルーして
プログラムの外側へ伝播します。つまり「隠さず、そのままエラーを出す」動作になり、
バグの原因がすぐに分かります。
finally ブロックは例外の有無にかかわらず必ず実行されます。
ファイルのクローズ・データベース接続の解放・ログの記録など、
「何があっても確実に実行したい」クリーンアップ処理に使います。
return や break で途中終了した場合でも finally は実行される点が重要です。
💡 try / except / else / finally の実行フロー:
| シナリオ | try | except | else | finally |
|---|---|---|---|---|
| 例外なし | ✅ 全体 | ❌ | ✅ | ✅ |
| 例外あり(捕捉) | ⛔ 途中 | ✅ | ❌ | ✅ |
| 例外あり(未捕捉) | ⛔ 途中 | ❌ | ❌ | ✅ → 伝播 |
💡 実際はwith文を使う: ファイルを with open(...) で開けば finally での手動クローズは不要です。with 文は内部的に __enter__ / __exit__ を使って自動クリーンアップを行います。finally はDB接続のクローズや一時ファイルの削除など、with 構文が使えない場面で真価を発揮します。
💡 finally が役立つ実用例:
- データベース接続の解放(
conn.close()を必ず実行) - ネットワークソケットのクローズ
- 処理の開始・終了ログの記録(成功・失敗にかかわらず処理完了を記録)
- 一時ファイルの削除(エラーで中断してもゴミファイルを残さない)
- プログレスバーのリセット(UI処理でエラー後も画面を正常状態に戻す)
4. raise
raise 例外型("メッセージ") を使うと、条件を満たさない入力に対して
意図的に例外を発生させられます。例えば年齢が負の値、SpO2 が 100 を超える値など、
医学的にあり得ない値を関数に渡された場合、早期にエラーを発生させることで
バグの原因を明確にできます(フェイル・ファストの原則)。
問題が発生した箇所と原因が一致するため、デバッグが格段に楽になります。
💡 raise と try/except の役割分担:
- 関数の中(raise):「この値はおかしい」と例外を投げるだけ。どう対処するかは関知しない
- 呼び出し元(try/except):投げられた例外を受け取る。エラーメッセージを表示したり、デフォルト値を使うなど対処を決める
validate_age(-5) を呼び出す → 関数内で raise ValueError(...) が実行される → 例外が呼び出し元の try ブロックに伝わる → except ValueError as e: で受け取る ← ここがなければプログラム停止
💡 raise(引数なし)— 例外を受け取りつつ上位に投げ直す:
except ブロック内で raise だけ書くと、受け取った例外をそのまま上位に再発生させます。
「エラーをログに記録したいが、例外を握りつぶさず呼び出し元にも伝えたい」場面で使います。
try:
validate_age(-5)
except ValueError as e:
print(f"[ログ] エラーを記録: {e}") # ログだけ残して…
raise # 例外を再び投げる → 呼び出し元にも伝わる
raise を書かずに except ブロックを終わらせると例外は握りつぶされ、呼び出し元はエラーが起きたことを知れません。意図的にエラーを無視するのでなければ、raise で伝播させるのが基本です。
5. カスタム例外
カスタム例外とは、標準の例外クラス(ValueError など)を継承して作る独自の例外クラスです。
class InvalidVitalError(ValueError): pass のように1行書くだけで定義できます。
ValueError をそのまま使っても動きますが、ValueError は
int("abc") のような型変換ミスでも発生します。
except ValueError と書いたとき、「バイタル値が不正」なのか「型変換に失敗」なのか区別できません。
カスタム例外を使えば except InvalidVitalError で「バイタル値の問題だけ」を確実に捕捉でき、
例外名自体が「何のエラーか」を説明するため、コードの意図も伝わりやすくなります。
💡 カスタム例外クラスの設計指針:
- クラス名は
Errorで終わる慣習(例:InvalidVitalError、DataValidationError) - 適切な標準例外を継承する(入力値の不正 →
ValueError、実行時の異常 →RuntimeError) - シンプルな用途なら
passだけでクラスを作ればOK。__init__は追加情報が必要なときだけ実装する - PyTorchの
RuntimeError: CUDA out of memoryやValueError: Expected input sizeなども同じ仕組みで定義されている
6. 練習問題
型変換のエラーハンドリング
文字列のリストを整数に変換する関数 parse_values(str_list) を作ってください。変換できない要素は None に置き換え、最後に変換成功・失敗の件数を表示します。
ヒントを見る(答え+解説)
def parse_values(str_list):
results = []
success = 0
fail = 0
for s in str_list:
try:
results.append(int(s))
success += 1
except ValueError:
results.append(None)
fail += 1
print(f"成功: {success}件, 失敗: {fail}件")
return results
data = ["120", "N/A", "98", "--", "155", "200", "不明"]
result = parse_values(data)
print(result)
# → 成功: 4件, 失敗: 3件
# → [120, None, 98, None, 155, 200, None]
try ブロックで変換を試み、失敗した場合は except ValueError で捕捉します。変換できない文字列を None に置き換えるパターンは、医療データの前処理でよく使います。
年齢バリデーションで raise を使う
年齢が0未満または150を超える場合に ValueError を発生させる関数 validate_age(age) を作ってください。正常な場合は True を返します。
ヒントを見る(答え+解説)
def validate_age(age):
if age < 0:
raise ValueError(f"年齢は0以上である必要があります: {age}")
if age > 150:
raise ValueError(f"年齢は150以下である必要があります: {age}")
return True
test_ages = [25, -5, 80, 200, 0]
for age in test_ages:
try:
if validate_age(age):
print(f"✅ {age}歳: 有効")
except ValueError as e:
print(f"❌ {age}歳: {e}")
# → ✅ 25歳: 有効 / ❌ -5歳: ... / ✅ 80歳: 有効 / ❌ 200歳: ...
raise ValueError("メッセージ") で意図的に例外を発生させます。呼び出し側で except ValueError as e と受け取ることで、バリデーションエラーを適切に処理できます。
finally でクリーンアップ
以下の関数に finally ブロックを追加し、例外の有無にかかわらず「処理完了ログを記録しました」と表示してください。
ヒントを見る(答え+解説)
def process_data(data):
try:
result = 100 / data
print(f"処理結果: {result:.2f}")
return result
except ZeroDivisionError:
print("エラー: ゼロ除算が発生しました")
return None
finally:
print("処理完了ログを記録しました")
process_data(5)
print()
process_data(0)
# → 処理結果: 20.00 / 処理完了ログを記録しました
# → エラー: ゼロ除算が発生しました / 処理完了ログを記録しました
finally ブロックは例外の有無にかかわらず必ず実行されます。ログ記録・ファイルのクローズ・DB接続の解放など、確実に実行したいクリーンアップ処理に使います。
まとめ
このレッスンのポイント
try/exceptでエラーを捕捉してプログラムのクラッシュを防ぐexcept 例外型 as e:で例外オブジェクトを取得し、詳細を確認else: 例外がなかった場合のみ実行(成功時の後処理)finally: 例外の有無にかかわらず必ず実行(クリーンアップ)raise 例外型("メッセージ")で意図的に例外を投げるclass MyError(Exception):でカスタム例外を定義できる
自由に試してみましょう:
完了するとトップページに進捗が表示されます
