Python の例外処理 — try、except、raise
このトピックを完了すると、次のことが理解できるようになります。
Python でエラーがどのように発生するか、try/except を使用してエラーをどのようにキャッチするか、raise を使用してエラーを直接発生させる方法、および finally を使用してクリーンアップ操作を実行する方法を理解します。
エラーはプログラムを停止させる
Python では、エラーが発生すると、プログラムは直ちに停止します。
numbers = [1, 2, 3]print(numbers[10]) # IndexError: list index out of rangeprint("This line never runs")エラーが発生した行以降のすべてのコードは実行されません。Web サーバーの場合、サーバー自体がクラッシュします。データ分析スクリプトの場合、1000 個の項目のうち 999 個を処理した後、完全に失敗します。
例外処理は、「プログラムがクラッシュすることなくエラーを処理する方法」です。
try/except — エラーのキャッチ
try: result = 10 / 0except ZeroDivisionError: print("Cannot divide by zero")
print("Program continues") # この行は実行されます!try ブロック内でエラーが発生した場合、プログラムは except ブロックにジャンプします。プログラムは停止せず、実行を継続します。
エラーメッセージの取得
try: value = int("hello")except ValueError as e: print(f"Error: {e}") # Error: invalid literal for int() with base 10: 'hello'as e を使用すると、エラーオブジェクトを受け取り、何が問題だったのかを確認できます。
複数のエラーを個別に処理する
def safe_divide(a, b): try: result = a / b return round(result, 2) except ZeroDivisionError: print("Denominator cannot be zero") return None except TypeError: print("Both arguments must be numbers") return None
safe_divide(10, 0) # Denominator cannot be zerosafe_divide("10", 2) # Both arguments must be numberssafe_divide(10, 3) # 3.33異なるエラータイプを異なる方法で処理できます。except Exception を使用して、すべてのエラーを一度にキャッチすることもできますが、推奨されません。デバッグが難しくなるためです。エラーが発生したときに、どのエラーが発生したのかわからなくなります。
一般的なエラータイプ
| エラー | 原因 | 例 |
|---|---|---|
ValueError | 無効な値 | int("abc") |
TypeError | 不適切な型 | "3" + 5 |
IndexError | インデックスが範囲外 | [1,2,3][10] |
KeyError | 辞書のキーが存在しない | {"a": 1}["b"] |
FileNotFoundError | ファイルが存在しない | open("none.txt") |
ZeroDivisionError | ゼロ除算 | 10 / 0 |
AttributeError | 存在しない属性/メソッドへのアクセス | None.upper() |
これらは、日常的なプログラミングで遭遇する最も一般的な 7 つのエラーです。
else と finally
try: f = open("data.txt", "r") content = f.read()except FileNotFoundError: print("File not found")else: # try ブロックが成功した場合にのみ実行されます print(f"Read {len(content)} characters")finally: # 常に実行されます(成功または失敗に関係なく) print("Cleanup done")| ブロック | 実行タイミング |
|---|---|
try | 常に試行 |
except | エラーが発生した場合 |
else | エラーなしで成功した場合 |
finally | 常に(成功と失敗の両方) |
finally は、ファイルやネットワーク接続を閉じたり、一時ファイルを削除したりするなど、クリーンアップ操作に使用されます。これは、エラーが発生したかどうかに関係なく、常に実行する必要があるコードです。
raise — エラーを直接発生させる
def withdraw(balance, amount): if amount <= 0: raise ValueError("Withdrawal amount must be positive") if amount > balance: raise ValueError(f"Insufficient funds: balance={balance}, requested={amount}") return balance - amount
try: new_balance = withdraw(1000, 5000)except ValueError as e: print(f"Transaction failed: {e}") # Transaction failed: Insufficient funds: balance=1000, requested=5000raise は、「この状況は通常ではない」と宣言する方法です。エラーを try/except を使用して処理する責任を呼び出し側に委譲します。
カスタムエラークラス
class InsufficientFundsError(Exception): def __init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__( f"Cannot withdraw {amount} from balance {balance}" )
def withdraw(balance, amount): if amount > balance: raise InsufficientFundsError(balance, amount) return balance - amount
try: withdraw(1000, 5000)except InsufficientFundsError as e: print(e) # Cannot withdraw 5000 from balance 1000 print(e.balance) # 1000 print(e.amount) # 5000Exception から継承して、独自のカスタムエラーを作成できます。これにより、エラーに追加情報(残高、要求額)を含めることができるため、エラー処理コードはより正確に応答できます。
実際のパターン — ファイル処理
def read_config(filepath): try: with open(filepath, "r", encoding="utf-8") as f: lines = f.readlines() except FileNotFoundError: print(f"Config file not found: {filepath}") return {} except PermissionError: print(f"Permission denied: {filepath}") return {} config = {} for i, line in enumerate(lines, 1): line = line.strip() if not line or line.startswith("#"): continue if "=" not in line: print(f"Warning: invalid format at line {i}: {line}") continue key, value = line.split("=", 1) config[key.strip()] = value.strip() return config
# config.txt:# host = localhost# port = 3000# # this is a commentsettings = read_config("config.txt")print(settings) # {'host': 'localhost', 'port': '3000'}このパターンは、実際のシナリオで非常に一般的です。
- ファイルを開くことができない場合は、
exceptで処理されます。 - 解析中に不正な行が見つかった場合は、警告のみが表示され、スキップされます。
- 空の行とコメント(
#)は無視されます。
プログラムは、構成ファイルを開くことができないという理由だけでクラッシュするのではなく、デフォルト値で動作するか、警告を表示します。
アンチパターン — してはいけないこと
すべてのエラーを無視する
# 悪い — エラーを飲み込むtry: risky_operation()except Exception: pass # 何が起こったのか?誰も知らない
# 良い — 少なくともログに記録するtry: risky_operation()except Exception as e: print(f"Warning: {e}") # または logging.warning(...)except: pass は、すべてのエラーを無視します。プログラムはクラッシュしませんが、何が問題だったのかわかりません。後でデバッグするのが非常に難しくなります。
広すぎる except
# 悪い — タイプミス(NameError)をキャッチするtry: valeu = int(input("Enter number: ")) # タイプミス: valeuexcept Exception: print("Invalid input") # NameError だが、"Invalid input" なのか?
# 良い — 予想されるエラーのみをキャッチするtry: value = int(input("Enter number: "))except ValueError: print("Invalid input — please enter a number")主要なポイント
| 構文 | 役割 | 使用するタイミング |
|---|---|---|
try | エラーが発生する可能性のあるコードをラップする | 外部入力、ファイル、ネットワークなど |
except | 特定のエラーをキャッチして処理する | 各エラータイプに対して異なる処理が必要な場合 |
else | 成功した場合にのみ実行する | エラーが発生しなかった場合に実行する追加のアクション |
finally | 常に実行する | クリーンアップ操作(ファイルのクローズ、接続の終了) |
raise | エラーを直接発生させる | 無効な入力、ビジネスルールの違反 |
例外処理の主な原則:予想されるエラーのみをキャッチし、できるだけ狭くキャッチし、無視しないこと。 すべてを try/except でラップすると安全になるという考え方は誤りです。不適切な例外処理は、エラーを隠し、例外がない場合よりもデバッグをさらに困難にする可能性があります。