Python Try Except:例外を正しく処理する方法
Updated on
あなたのPythonスクリプトはファイルを読み取り、データを解析し、APIに送信します。あなたのマシンでは完璧に動作します。しかし、ファイルパスが間違っている、JSONが不正な形式である、またはネットワークがダウンしているサーバーで実行すると、プログラムはトレースバックでクラッシュし、パイプライン全体が停止します。これがtry/exceptが解決する問題です。エラーにプログラムを終了させる代わりに、エラーをキャッチし、処理し、実行を継続します。
Pythonの例外とは何か?
例外とは、プログラムの通常のフローを中断するイベントです。Pythonが実行できない操作に遭遇したとき──ゼロ除算、存在しないディクショナリキーへのアクセス、存在しないファイルの開封──例外オブジェクトを作成し、実行を停止します。その例外を何もキャッチしなければ、プログラムは終了してトレースバックを表示します。
# This crashes the program
result = 10 / 0出力:
Traceback (most recent call last):
File "example.py", line 2, in <module>
result = 10 / 0
ZeroDivisionError: division by zero例外は構文エラーとは異なります。構文エラーは、Pythonがコードを全く解析できないことを意味します。例外は、コードが正常に解析された後、実行中に発生します。
基本的なTry/Except構文
try/exceptブロックは、失敗する可能性のあるコードを試行し、失敗した場合の処理を定義できます。
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero.")出力:
Cannot divide by zero.プログラムはクラッシュしません。Pythonはtry内のコードを実行します。ZeroDivisionErrorが発生すると、実行はexceptブロックにジャンプします。try/exceptの後のすべてが通常通り続行されます。
例外オブジェクトをキャプチャしてエラーメッセージを確認することもできます:
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error: {e}")出力:
Error: division by zero特定の例外をキャッチする
Pythonには数十の組み込み例外タイプがあります。正しいものをキャッチすることで、コードが正確になります。以下は最も一般的に処理する例外です。
ValueError
関数が正しい型だが不適切な値の引数を受け取ったときに発生します。
try:
number = int("not_a_number")
except ValueError as e:
print(f"Invalid value: {e}")出力:
Invalid value: invalid literal for int() with base 10: 'not_a_number'TypeError
間違った型のオブジェクトに操作が適用されたときに発生します。
try:
result = "hello" + 42
except TypeError as e:
print(f"Type error: {e}")出力:
Type error: can only concatenate str (not "int") to strFileNotFoundError
存在しないファイルを開こうとしたときに発生します。
try:
with open("nonexistent_file.txt", "r") as f:
content = f.read()
except FileNotFoundError:
print("File not found. Check the file path.")KeyError
存在しないディクショナリキーにアクセスしたときに発生します。
data = {"name": "Alice", "age": 30}
try:
email = data["email"]
except KeyError as e:
print(f"Missing key: {e}")出力:
Missing key: 'email'IndexError
範囲外のリストインデックスにアクセスしたときに発生します。
items = [10, 20, 30]
try:
value = items[5]
except IndexError:
print("Index out of range.")複数のExceptブロック
異なる例外タイプを個別のexceptブロックで処理できます。Pythonは順番にチェックし、最初にマッチしたものを実行します。
def parse_config(raw_value):
try:
parts = raw_value.split(":")
key = parts[0]
value = int(parts[1])
return {key: value}
except IndexError:
print("Config format error: missing colon separator.")
except ValueError:
print("Config format error: value is not a number.")
except AttributeError:
print("Config format error: input is not a string.")
parse_config("timeout:30") # Returns {'timeout': 30}
parse_config("timeout") # Config format error: missing colon separator.
parse_config("timeout:abc") # Config format error: value is not a number.
parse_config(12345) # Config format error: input is not a string.タプルを使用して、1つのexceptブロックで複数の例外をキャッチすることもできます:
try:
value = int(input("Enter a number: "))
result = 100 / value
except (ValueError, ZeroDivisionError) as e:
print(f"Invalid input: {e}")Elseブロック
elseブロックはtryブロックで例外が発生しなかった場合にのみ実行されます。「正常パス」のコードをエラー処理コードから分離します。
try:
number = int("42")
except ValueError:
print("That is not a valid number.")
else:
print(f"Successfully parsed: {number}")出力:
Successfully parsed: 42elseブロックが便利なのは、その中で発生した例外が先行するexceptブロックではキャッチされないためです。これにより、成功パスコード内のバグを誤って隠すことを防ぎます。
filename = "data.txt"
try:
f = open(filename, "r")
except FileNotFoundError:
print(f"File '{filename}' does not exist.")
else:
content = f.read()
f.close()
print(f"Read {len(content)} characters.")open()が失敗した場合、exceptブロックが処理します。open()が成功した場合、elseブロックがファイルを読み取ります。f.read()中のエラーはここではキャッチされません──上位に伝播しますが、これが正しい動作です。
Finallyブロック
finallyブロックは例外が発生したかどうかにかかわらず常に実行されます。クリーンアップコードの適切な場所です:ファイルのクローズ、ロックの解放、データベースからの切断。
f = None
try:
f = open("data.txt", "r")
content = f.read()
except FileNotFoundError:
print("File not found.")
finally:
if f is not None:
f.close()
print("File handle closed.")finallyブロックはtryブロックが値を返した場合や、処理されない例外が発生した場合でも実行されます。
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
finally:
print("Division attempted.")
result = divide(10, 3)
# Output: Division attempted.
# result = 3.3333333333333335
result = divide(10, 0)
# Output: Division attempted.
# result = None完全なTry/Except/Else/Finallyパターン
4つのブロックすべてがどのように連携するかを示します:
import json
def load_config(filepath):
"""Load and parse a JSON config file."""
f = None
try:
f = open(filepath, "r")
data = json.load(f)
except FileNotFoundError:
print(f"Config file '{filepath}' not found.")
return {}
except json.JSONDecodeError as e:
print(f"Invalid JSON in '{filepath}': {e}")
return {}
else:
print(f"Config loaded successfully with {len(data)} keys.")
return data
finally:
if f is not None:
f.close()
print("File handle released.")
config = load_config("settings.json")| ブロック | 実行タイミング | 目的 |
|---|---|---|
try | 常に(最初) | 例外を発生させる可能性のあるコードを含む |
except | 一致する例外が発生した場合のみ | エラーを処理する |
else | tryで例外が発生しなかった場合のみ | 成功パスのロジックを実行する |
finally | 常に(最後) | 必ず実行されるクリーンアップコード |
一般的な組み込み例外リファレンス
Pythonには組み込み例外の階層構造が含まれています。以下は最も頻繁に遭遇するものです。
| 例外 | 発生条件 | 例 |
|---|---|---|
Exception | ほとんどの例外の基底クラス | すべての非システム終了例外の親 |
ValueError | 正しい型だが不適切な値 | int("abc") |
TypeError | 操作に対して不適切な型 | "text" + 5 |
KeyError | ディクショナリキーが存在しない | d["missing"] |
IndexError | リストインデックスが範囲外 | [1,2,3][10] |
FileNotFoundError | ファイルが存在しない | open("no.txt") |
ZeroDivisionError | ゼロによる除算またはモジュロ | 1 / 0 |
AttributeError | オブジェクトに属性がない | None.append(1) |
ImportError | モジュールのインポートが失敗 | import nonexistent |
OSError | OS操作が失敗 | ディスクフル、アクセス拒否 |
StopIteration | イテレータに要素がない | 使い果たされたイテレータでのnext() |
RuntimeError | 一般的なランタイムエラー | その他のエラーのキャッチオール |
これらはすべてBaseExceptionを継承しています。実際には、Exceptionまたはそのサブクラスをキャッチすべきであり、BaseExceptionを直接キャッチすべきではありません(SystemExitやKeyboardInterruptを含むため)。
raiseで例外を発生させる
コードが無効な条件を検出したとき、明示的に例外を発生させることができます。これにより前提条件を強制し、呼び出し元にエラーを通知できます。
def set_age(age):
if not isinstance(age, int):
raise TypeError(f"Age must be an integer, got {type(age).__name__}")
if age < 0 or age > 150:
raise ValueError(f"Age must be between 0 and 150, got {age}")
return age
# Valid usage
print(set_age(25)) # 25
# Invalid usage
try:
set_age(-5)
except ValueError as e:
print(e) # Age must be between 0 and 150, got -5
try:
set_age("thirty")
except TypeError as e:
print(e) # Age must be an integer, got strexceptブロック内で引数なしのraiseを使用して、現在の例外を再発生させることもできます。これはエラーをログに記録してから伝播させたい場合に便利です。
import logging
try:
result = 10 / 0
except ZeroDivisionError:
logging.error("Division by zero encountered")
raise # re-raises the original ZeroDivisionErrorカスタム例外クラス
大規模なプロジェクトでは、独自の例外クラスを定義します。カスタム例外はエラー処理をより読みやすくし、呼び出し元が特定の障害モードをキャッチできるようにします。
class ValidationError(Exception):
"""Raised when input data fails validation."""
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"Validation failed on '{field}': {message}")
class DatabaseConnectionError(Exception):
"""Raised when the database connection fails."""
pass
# Usage
def validate_email(email):
if "@" not in email:
raise ValidationError("email", "Must contain @ symbol")
if "." not in email.split("@")[1]:
raise ValidationError("email", "Domain must contain a dot")
return email
try:
validate_email("userexample.com")
except ValidationError as e:
print(e) # Validation failed on 'email': Must contain @ symbol
print(e.field) # email
print(e.message) # Must contain @ symbolカスタム例外はBaseExceptionではなくExceptionを継承すべきです。関連する例外を共通の基底クラスの下にグループ化して、呼び出し元が広いまたは狭いカテゴリをキャッチできるようにします:
class AppError(Exception):
"""Base exception for this application."""
pass
class ConfigError(AppError):
pass
class NetworkError(AppError):
pass
# Caller can catch all app errors or specific ones
try:
raise NetworkError("Connection timed out")
except AppError as e:
print(f"Application error: {e}")Python例外処理のベストプラクティス
1. ベアな例外をキャッチしない
ベアなexcept:はKeyboardInterruptやSystemExitを含むすべてをキャッチします。これはバグを隠し、デバッグを悪夢にします。
# BAD - catches everything, hides real bugs
try:
do_something()
except:
pass
# GOOD - catches specific exceptions
try:
do_something()
except ValueError as e:
logging.warning(f"Invalid value: {e}")広い範囲をキャッチする必要がある場合は、代わりにexcept Exceptionを使用してください。これでもKeyboardInterruptとSystemExitは伝播します。
2. Tryブロックを小さく保つ
例外を発生させる可能性のあるコードのみをtryブロック内に置きます。大きなtryブロックでは、どの行がエラーを引き起こしたか不明確になります。
# BAD - too much code in try
try:
data = load_data()
cleaned = clean_data(data)
result = analyze(cleaned)
save_results(result)
except Exception as e:
print(f"Something failed: {e}")
# GOOD - narrow try blocks
data = load_data()
cleaned = clean_data(data)
try:
result = analyze(cleaned)
except ValueError as e:
print(f"Analysis failed: {e}")
result = default_result()
save_results(result)3. 例外をログに記録し、隠さない
passで例外を飲み込むと、目に見えないバグが生まれます。常にエラーをログに記録するか報告してください。
import logging
try:
process_record(record)
except ValueError as e:
logging.error(f"Failed to process record {record['id']}: {e}")4. 具体的な例外タイプを使用する
可能な限り具体的な例外タイプをキャッチしてください。これにより、予期しないエラーを誤って処理することを防ぎます。
| アプローチ | キャッチ対象 | リスクレベル |
|---|---|---|
except: | SystemExitを含むすべて | 非常に高い |
except Exception: | すべての標準例外 | 高い |
except ValueError: | ValueErrorのみ | 低い |
except (ValueError, TypeError): | 2つの特定タイプ | 低い |
5. Finallyでリソースをクリーンアップするかコンテキストマネージャを使用する
ファイルハンドル、データベース接続、ロックには、常にfinallyまたは(さらに良いのは)with文を使用してください。
# Prefer context managers for resource cleanup
with open("data.txt", "r") as f:
content = f.read()
# File is automatically closed, even if an exception occurs実世界の例
JSONファイルの読み取りと解析
import json
def read_json_config(filepath):
"""Read a JSON configuration file with proper error handling."""
try:
with open(filepath, "r") as f:
config = json.load(f)
except FileNotFoundError:
print(f"Config file not found: {filepath}")
return None
except PermissionError:
print(f"No permission to read: {filepath}")
return None
except json.JSONDecodeError as e:
print(f"Invalid JSON at line {e.lineno}, column {e.colno}: {e.msg}")
return None
else:
print(f"Loaded config with keys: {list(config.keys())}")
return config
config = read_json_config("app_config.json")
if config:
db_host = config.get("database_host", "localhost")HTTP APIコールの実行
import urllib.request
import urllib.error
import json
def fetch_user(user_id):
"""Fetch user data from an API with retry logic."""
url = f"https://jsonplaceholder.typicode.com/users/{user_id}"
max_retries = 3
for attempt in range(1, max_retries + 1):
try:
with urllib.request.urlopen(url, timeout=5) as response:
data = json.loads(response.read().decode())
return data
except urllib.error.HTTPError as e:
if e.code == 404:
print(f"User {user_id} not found.")
return None
print(f"HTTP error {e.code} on attempt {attempt}")
except urllib.error.URLError as e:
print(f"Network error on attempt {attempt}: {e.reason}")
except json.JSONDecodeError:
print("API returned invalid JSON.")
return None
print(f"All {max_retries} attempts failed.")
return None
user = fetch_user(1)
if user:
print(f"Found user: {user['name']}")CSVデータの処理
import csv
def process_sales_data(filepath):
"""Process a CSV file with robust error handling."""
results = []
try:
with open(filepath, "r", newline="") as f:
reader = csv.DictReader(f)
for row_num, row in enumerate(reader, start=2):
try:
amount = float(row["amount"])
quantity = int(row["quantity"])
results.append({
"product": row["product"],
"total": amount * quantity,
})
except KeyError as e:
print(f"Row {row_num}: Missing column {e}")
except ValueError as e:
print(f"Row {row_num}: Invalid number - {e}")
except FileNotFoundError:
print(f"File not found: {filepath}")
except PermissionError:
print(f"Cannot read file: {filepath}")
return resultsTry/Except vs If/Else:どちらを使うべきか
Pythonは EAFP原則に従います:"Easier to Ask Forgiveness than Permission"(許可を求めるより許しを請う方が簡単)。これはLBYLアプローチ:"Look Before You Leap"(飛ぶ前に見よ)と対照的です。
# LBYL (Look Before You Leap) - using if/else
if "email" in user_data:
email = user_data["email"]
else:
email = "unknown"
# EAFP (Easier to Ask Forgiveness) - using try/except
try:
email = user_data["email"]
except KeyError:
email = "unknown"| 基準 | if/else (LBYL) | try/except (EAFP) |
|---|---|---|
| 最適な場面 | チェックが安価でエラーが頻繁 | エラーがまれまたはチェックが高コスト |
| パフォーマンス(エラーなし) | やや遅い(毎回追加チェック) | やや速い(チェックのオーバーヘッドなし) |
| パフォーマンス(エラー発生時) | 同じ | 遅い(例外作成にオーバーヘッド) |
| 競合状態 | あり得る(チェックと使用の間に状態が変わる可能性) | なし(アトミック操作) |
| 可読性 | 単純な条件には明確 | 複数の方法で失敗する可能性がある操作に最適 |
| ファイル操作 | if os.path.exists(path) -- チェックとオープンの間にファイルが削除される可能性 | try: open(path) -- 実際の失敗を処理 |
| ディクショナリアクセス | if key in dict -- シンプルで高速 | try: dict[key] -- またはdict.get(key, default)を使用 |
try/exceptを使用する場面:
- 失敗ケースがまれ(例外は「エラーなし」パスに最適化されている)。
- チェック自体が操作と同じくらい高コスト(例:ファイルが存在するか確認してから開く)。
- 一連の操作で複数の問題が発生する可能性がある。
- 競合状態が問題になる(特にファイルとネットワークリソース)。
if/elseを使用する場面:
- 条件のチェックが安価で、失敗が頻繁。
- 処理前にユーザー入力を検証する。
- ロジックが条件文としてより明確に読める。
RunCellで例外をより速くデバッグする
Jupyterノートブックで作業していると、例外が分析フローを中断することがあります。DataFrameの5万行目でKeyErrorに遭遇したり、パイプラインの3セル先でTypeErrorが表面化したりします。根本原因を追跡するには、トレースバックをスクロールし、手動で変数を検査する必要があります。
RunCell (opens in a new tab)は、Jupyter内で直接動作するAIエージェントです。完全なトレースバックを読み取り、現在のスコープの変数を検査し、コンテキスト内で修正を提案します。例外処理でどのように役立つかを説明します:
- トレースバック分析。 RunCellは例外チェーンを解析し、ネストされた関数呼び出しでも、どの変数や操作が失敗を引き起こしたかを特定します。
- 修正提案。 Stack Overflowを検索する代わりに、RunCellはすぐに実行できる修正されたコードセルを生成します。
try/exceptを追加すべきか、型変換を修正すべきか、欠落キーを処理すべきかを判断します。 - 予防チェック。 RunCellはコードをスキャンし、例外を発生させる可能性のある操作──
.get()なしでのディクショナリキーアクセスや、ゼロチェックなしでの除算──をセル実行前に警告します。
RunCellは既存のJupyter環境内で動作するため、実際のデータと変数にアクセスできます。提供される提案は一般的なアドバイスではなく、あなたの状況に特化したものです。
FAQ
Pythonのtry/exceptとtry/catchの違いは何ですか?
Pythonはtry/exceptを使用し、try/catchは使用しません。try/catch構文はJava、JavaScript、C++などの言語に属します。Pythonではキーワードはexceptです。機能は同じです:失敗する可能性のあるコードを試行し、失敗した場合のハンドラを定義します。
Pythonで複数のexceptブロックを使用できますか?
はい。必要な数だけexceptブロックを連鎖でき、それぞれ異なる例外タイプをキャッチします。Pythonは順番に評価し、最初にマッチしたブロックを実行します。タプルを使用して1つのブロックで複数の例外をキャッチすることもできます:except (ValueError, TypeError) as e:。
try/exceptでelseをいつ使うべきですか?
tryブロックが成功した場合にのみ実行すべきコードがある場合にelseブロックを使用します。主な利点は、elseブロック内で発生した例外が先行するexceptブロックでキャッチされないため、無関係なエラーを誤って隠すことを防ぐことです。
Pythonでfinallyは常に実行されますか?
はい。finallyブロックは、tryブロックが正常に完了した場合、処理された例外を発生させた場合、または処理されない例外を発生させた場合のいずれでも実行されます。tryまたはexceptブロックにreturn文が含まれていても実行されます。唯一の例外は、Pythonプロセスが外部から強制終了された場合、またはos._exit()が呼び出された場合です。
Pythonでカスタム例外を作成するにはどうすればよいですか?
Exceptionを継承する新しいクラスを作成します。カスタム属性を追加し、__init__メソッドをオーバーライドできます。例:class MyError(Exception): pass。より複雑な場合には、エラーコードやコンテキストデータなどのフィールドを追加します。常にBaseExceptionではなくExceptionを継承してください。
まとめ
Pythonのtry/exceptはランタイムエラーを処理する標準的なメカニズムです。特定の例外をキャッチし、クリーンアップコードを実行し、問題が発生してもプログラムを安定させることができます。完全なパターン──try/except/else/finally──はすべてのシナリオをカバーします:操作の試行、失敗の処理、成功ロジックの実行、リソースのクリーンアップ。
主要な原則は明快です。広い例外ではなく、具体的な例外をキャッチしましょう。tryブロックを小さく保ちましょう。エラーを隠すのではなく、常にログに記録するか報告しましょう。クリーンアップにはfinallyまたはコンテキストマネージャを使用しましょう。自分のコードでは明確なエラーメッセージとともに意味のある例外を発生させましょう。
ファイルの読み取り、APIコール、ユーザー入力の処理のいずれであっても、適切な例外処理は午前2時にクラッシュするスクリプトと、エラーをログに記録して実行を継続するスクリプトの違いです。基本から始めましょう──失敗する可能性のあるコードを囲む簡単なtry/except──そしてプロジェクトの成長に伴い、カスタム例外階層を構築していきましょう。