Python例外処理ベストプラクティス完全ガイド
例外処理は、プログラム実行中に発生するエラーを適切に処理するための仕組みです。堅牢なPythonアプリケーションを構築するためには、例外処理のベストプラクティスを理解し、適切に実装することが不可欠です。
目次
基本的な例外処理:try-except文の正しい使い方
基本構文とシンプルな例外キャッチ
# 基本的なtry-except構文
def safe_division(a, b):
try:
result = a / b
return result
except ZeroDivisionError:
print("ゼロで割ることはできません")
return None
# 使用例
result = safe_division(10, 2) # 5.0
result = safe_division(10, 0) # None
複数の例外を処理する方法
def process_user_input(user_input):
try:
number = int(user_input)
result = 100 / number
return f"結果: {result}"
except ValueError:
return "数値を入力してください"
except ZeroDivisionError:
return "ゼロは入力できません"
except Exception as e:
return f"予期しないエラー: {e}"
# 使用例
print(process_user_input("5")) # 結果: 20.0
print(process_user_input("abc")) # 数値を入力してください
中級レベル:else文とfinally文の活用
else文による正常処理の分離
def read_file_safely(filename):
try:
file = open(filename, 'r', encoding='utf-8')
except FileNotFoundError:
print(f"ファイル '{filename}' が見つかりません")
return None
else:
# 例外が発生しなかった場合のみ実行
content = file.read()
file.close()
return content
finally:
# 必ず実行される処理
print("ファイル処理が完了しました")
# 使用例
content = read_file_safely("example.txt")
finally文によるリソース管理
def database_operation():
connection = None
try:
connection = get_database_connection()
execute_query(connection, "SELECT * FROM users")
return True
except DatabaseError as e:
print(f"データベースエラー: {e}")
return False
finally:
# リソースの確実な解放
if connection:
connection.close()
print("データベース接続を閉じました")
def get_database_connection():
# データベース接続のシミュレーション
return "mock_connection"
def execute_query(connection, query):
# クエリ実行のシミュレーション
pass
上級テクニック:カスタム例外とコンテキストマネージャー
カスタム例外クラスの作成
class ValidationError(Exception):
"""データ検証エラー用のカスタム例外"""
def __init__(self, message, error_code=None):
self.message = message
self.error_code = error_code
super().__init__(self.message)
class UserService:
@staticmethod
def validate_email(email):
if "@" not in email:
raise ValidationError(
"有効なメールアドレスではありません",
error_code="INVALID_EMAIL"
)
return True
# 使用例
try:
UserService.validate_email("invalid-email")
except ValidationError as e:
print(f"エラー: {e.message}, コード: {e.error_code}")
with文によるリソース管理
class ManagedResource:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f"リソース '{self.name}' を取得")
return self
def __exit__(self, exc_type, exc_value, traceback):
print(f"リソース '{self.name}' を解放")
if exc_type:
print(f"例外が発生しました: {exc_value}")
return False # 例外を再発生させる
# 使用例
with ManagedResource("データベース接続") as resource:
print("リソースを使用中...")
# raise Exception("テストエラー") # 例外テスト用
ベストプラクティス1:具体的な例外をキャッチする
悪い例:広すぎる例外キャッチ
# 避けるべき書き方
def bad_exception_handling():
try:
risky_operation()
except Exception: # 広すぎる例外キャッチ
print("何かエラーが発生しました")
def risky_operation():
pass
良い例:具体的な例外の処理
# 推奨される書き方
def good_exception_handling():
try:
process_data()
except FileNotFoundError:
print("設定ファイルが見つかりません")
create_default_config()
except PermissionError:
print("ファイルへのアクセス権限がありません")
except ValueError as e:
print(f"データの形式が不正です: {e}")
def process_data():
pass
def create_default_config():
pass
ベストプラクティス2:ログ記録と例外情報の保持
適切なログ記録
import logging
import traceback
# ログ設定
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def process_with_logging(data):
try:
result = complex_calculation(data)
logger.info(f"処理成功: {result}")
return result
except ValueError as e:
logger.error(f"値エラー: {e}", exc_info=True)
raise # 例外を再発生
except Exception as e:
logger.critical(f"予期しないエラー: {e}")
logger.debug(traceback.format_exc())
raise
def complex_calculation(data):
if not isinstance(data, (int, float)):
raise ValueError("数値が必要です")
return data * 2
例外チェーニング
def parse_config_file(filename):
try:
with open(filename, 'r') as file:
config_data = file.read()
return parse_json_config(config_data)
except FileNotFoundError as e:
# 元の例外を保持しながら新しい例外を発生
raise ConfigurationError(
f"設定ファイル '{filename}' が見つかりません"
) from e
def parse_json_config(data):
import json
try:
return json.loads(data)
except json.JSONDecodeError as e:
raise ConfigurationError("JSON形式が不正です") from e
class ConfigurationError(Exception):
pass
ベストプラクティス3:例外の早期検出と明確なエラーメッセージ
引数検証とアサーション
def calculate_interest(principal, rate, years):
# 早期検証
if not isinstance(principal, (int, float)) or principal <= 0:
raise ValueError("元本は正の数値である必要があります")
if not isinstance(rate, (int, float)) or rate < 0:
raise ValueError("利率は0以上の数値である必要があります")
if not isinstance(years, int) or years <= 0:
raise ValueError("年数は正の整数である必要があります")
# アサーションによる内部検証
assert years <= 100, "年数は100年以下である必要があります"
return principal * (1 + rate) ** years
# 使用例
try:
result = calculate_interest(1000, 0.05, 10)
print(f"複利計算結果: {result}")
except ValueError as e:
print(f"入力エラー: {e}")
デコレータによる例外処理
from functools import wraps
def handle_exceptions(default_return=None, log_errors=True):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
if log_errors:
logger.error(f"{func.__name__}でエラー: {e}")
return default_return
return wrapper
return decorator
@handle_exceptions(default_return=[], log_errors=True)
def get_user_data(user_id):
if user_id <= 0:
raise ValueError("無効なユーザーID")
# データベースからユーザー情報を取得
return [{"id": user_id, "name": "テストユーザー"}]
# 使用例
data = get_user_data(-1) # []が返される
ベストプラクティス4:パフォーマンスを考慮した例外処理
EAFP vs LBYL
# EAFP (Easier to Ask for Forgiveness than Permission)
def eafp_approach(data_dict, key):
try:
return data_dict[key]
except KeyError:
return "デフォルト値"
# LBYL (Look Before You Leap)
def lbyl_approach(data_dict, key):
if key in data_dict:
return data_dict[key]
else:
return "デフォルト値"
# Pythonらしい書き方(EAFP)を推奨
data = {"name": "Python", "version": "3.9"}
result = eafp_approach(data, "description") # デフォルト値
例外処理のオーバーヘッド最小化
def optimized_file_processing(filenames):
results = []
for filename in filenames:
try:
# ファイル処理を一括で行う
with open(filename, 'r') as file:
content = file.read()
processed = process_content(content)
results.append(processed)
except (FileNotFoundError, PermissionError) as e:
# 軽量なエラーハンドリング
results.append(None)
logger.warning(f"ファイル処理スキップ: {filename}")
return results
def process_content(content):
return content.upper()
実践的な例外処理パターン
Webアプリケーションでの例外処理
class APIError(Exception):
def __init__(self, message, status_code=500):
self.message = message
self.status_code = status_code
super().__init__(self.message)
def api_endpoint(user_id):
try:
# ユーザー存在確認
if not user_exists(user_id):
raise APIError("ユーザーが見つかりません", 404)
# データ取得
user_data = get_user_profile(user_id)
return {"success": True, "data": user_data}
except APIError:
raise # APIエラーはそのまま再発生
except DatabaseConnectionError:
raise APIError("データベースに接続できません", 503)
except Exception as e:
logger.error(f"予期しないエラー: {e}")
raise APIError("内部サーバーエラー", 500)
def user_exists(user_id):
return user_id > 0
def get_user_profile(user_id):
return {"id": user_id, "name": "テストユーザー"}
class DatabaseConnectionError(Exception):
pass
非同期処理での例外処理
import asyncio
async def async_task_with_error_handling():
tasks = []
for i in range(5):
task = asyncio.create_task(risky_async_operation(i))
tasks.append(task)
results = []
for task in tasks:
try:
result = await task
results.append(result)
except AsyncOperationError as e:
logger.warning(f"非同期タスクエラー: {e}")
results.append(None)
return results
async def risky_async_operation(task_id):
await asyncio.sleep(0.1)
if task_id == 3:
raise AsyncOperationError(f"タスク{task_id}で失敗")
return f"タスク{task_id}完了"
class AsyncOperationError(Exception):
pass
# 使用例
# results = asyncio.run(async_task_with_error_handling())
テストでの例外処理
pytest での例外テスト
import pytest
def divide_numbers(a, b):
if b == 0:
raise ValueError("ゼロで割ることはできません")
return a / b
# 例外が発生することをテスト
def test_divide_by_zero():
with pytest.raises(ValueError, match="ゼロで割ることはできません"):
divide_numbers(10, 0)
# 正常ケースのテスト
def test_divide_normal():
result = divide_numbers(10, 2)
assert result == 5.0
# カスタム例外のテスト
def test_custom_exception():
with pytest.raises(ValidationError) as exc_info:
UserService.validate_email("invalid")
assert exc_info.value.error_code == "INVALID_EMAIL"
例外処理のアンチパターンとその対策
アンチパターン1:例外の無視
# 悪い例:例外を無視
try:
risky_operation()
except Exception:
pass # 例外を無視するのは危険
# 良い例:適切な処理
try:
risky_operation()
except SpecificException as e:
logger.warning(f"操作をスキップ: {e}")
return default_value()
アンチパターン2:例外を制御フローに使用
# 悪い例:例外を制御フローに使用
def bad_flow_control():
try:
while True:
item = get_next_item()
process(item)
except StopIteration:
pass # 終了条件として例外を使用
# 良い例:適切な制御フロー
def good_flow_control():
while True:
item = get_next_item()
if item is None:
break
process(item)
def get_next_item():
return None # 実際の実装では適切な値を返す
def process(item):
pass
まとめ:例外処理のベストプラクティス
効果的なPython例外処理は、コードの堅牢性と保守性を大幅に向上させます。以下の原則を守ることで、質の高いコードを書くことができます。
重要なポイント
- 具体的な例外をキャッチ: 広すぎる例外処理を避ける
- 適切なログ記録: エラー情報を適切に記録・保持
- 早期検証: 問題を早期に検出し、明確なエラーメッセージを提供
- リソース管理: with文やfinallyを使った確実なリソース解放
- 例外チェーニング: 元の例外情報を保持しながら新しい例外を発生
避けるべきパターン
- 例外の無視や隠蔽
- 制御フローとしての例外使用
- 過度に広い例外キャッチ
- 不適切なリソース管理
継続的な学習と実践により、堅牢で保守性の高いPythonアプリケーションを構築しましょう。
■「らくらくPython塾」が切り開く「呪文コーディング」とは?
■プロンプトだけでオリジナルアプリを開発・公開してみた!!
■AI時代の第一歩!「AI駆動開発コース」はじめました!
テックジム東京本校で先行開始。
■テックジム東京本校
「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。
<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。
<月1開催>放送作家による映像ディレクター養成講座
<オンライン無料>ゼロから始めるPython爆速講座


