チュートリアル

Python実践編:例外処理

Python例外処理エラーハンドリング実践
広告エリア

はじめに

プログラムは予期しないエラーに遭遇することがあります。例外処理を適切に行うことで、エラー発生時も graceful に対応できます。

基本的な例外処理

try-except文

try:
    result = 10 / 0
except ZeroDivisionError:
    print("0で割ることはできません")

# プログラムは続行される
print("処理を続けます")

複数の例外をキャッチ

try:
    value = int(input("数字を入力: "))
    result = 10 / value
except ValueError:
    print("数字を入力してください")
except ZeroDivisionError:
    print("0以外を入力してください")

# まとめてキャッチ
try:
    value = int(input("数字を入力: "))
    result = 10 / value
except (ValueError, ZeroDivisionError) as e:
    print(f"エラー: {e}")

例外オブジェクトの取得

try:
    with open("nonexistent.txt") as f:
        content = f.read()
except FileNotFoundError as e:
    print(f"ファイルが見つかりません: {e.filename}")
    print(f"エラーメッセージ: {e}")

else と finally

try:
    result = 10 / 2
except ZeroDivisionError:
    print("エラー発生")
else:
    # 例外が発生しなかった場合のみ実行
    print(f"結果: {result}")
finally:
    # 例外の有無に関わらず必ず実行
    print("処理完了")

finallyの活用例

def read_file(path):
    f = None
    try:
        f = open(path, "r")
        return f.read()
    except FileNotFoundError:
        return None
    finally:
        if f:
            f.close()  # 必ずファイルを閉じる

主要な組み込み例外

例外説明
Exceptionほぼ全ての例外の基底クラス
ValueError値が不正
TypeError型が不正
KeyError辞書にキーが存在しない
IndexErrorインデックスが範囲外
FileNotFoundErrorファイルが見つからない
AttributeError属性が存在しない
ImportErrorインポートに失敗
RuntimeError実行時エラー
# よくある例外の例
try:
    d = {"a": 1}
    print(d["b"])  # KeyError
except KeyError as e:
    print(f"キー {e} が存在しません")

try:
    lst = [1, 2, 3]
    print(lst[10])  # IndexError
except IndexError:
    print("インデックスが範囲外です")

例外を発生させる(raise)

def divide(a, b):
    if b == 0:
        raise ValueError("0で割ることはできません")
    return a / b

try:
    result = divide(10, 0)
except ValueError as e:
    print(e)

例外の再送出

try:
    result = 10 / 0
except ZeroDivisionError:
    print("ログに記録")
    raise  # 例外を再送出

独自例外の作成

class ValidationError(Exception):
    """入力値検証エラー"""
    pass

class AgeValidationError(ValidationError):
    """年齢検証エラー"""
    def __init__(self, age, message="年齢が不正です"):
        self.age = age
        self.message = message
        super().__init__(self.message)

def validate_age(age):
    if age < 0:
        raise AgeValidationError(age, "年齢は0以上である必要があります")
    if age > 150:
        raise AgeValidationError(age, "年齢が大きすぎます")
    return True

try:
    validate_age(-5)
except AgeValidationError as e:
    print(f"エラー: {e.message} (入力値: {e.age})")

コンテキストマネージャと例外

class DatabaseConnection:
    def __enter__(self):
        print("接続を開始")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("接続を終了")
        if exc_type is not None:
            print(f"例外が発生: {exc_val}")
        return False  # 例外を再送出

with DatabaseConnection() as conn:
    print("処理中...")
    # raise ValueError("テストエラー")

ベストプラクティス

1. 具体的な例外をキャッチする

# 悪い例
try:
    do_something()
except:  # 全ての例外をキャッチ
    pass

# 良い例
try:
    do_something()
except SpecificError as e:
    handle_error(e)

2. 例外を握りつぶさない

# 悪い例
try:
    data = json.loads(text)
except:
    data = {}  # エラーを隠してしまう

# 良い例
try:
    data = json.loads(text)
except json.JSONDecodeError as e:
    logger.error(f"JSON解析エラー: {e}")
    raise  # または適切なデフォルト値を返す

3. EAFP vs LBYL

# LBYL (Look Before You Leap)
if key in dictionary:
    value = dictionary[key]
else:
    value = default

# EAFP (Easier to Ask Forgiveness than Permission)
# Pythonではこちらが推奨
try:
    value = dictionary[key]
except KeyError:
    value = default

# さらに良い方法
value = dictionary.get(key, default)

4. ログと例外の連携

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def process_data(data):
    try:
        result = complex_operation(data)
        return result
    except ValueError as e:
        logger.warning(f"値エラー: {e}")
        return None
    except Exception as e:
        logger.exception("予期しないエラーが発生")
        raise

実践例:APIクライアント

import requests

class APIError(Exception):
    """API関連のエラー"""
    def __init__(self, status_code, message):
        self.status_code = status_code
        self.message = message
        super().__init__(f"[{status_code}] {message}")

class APIClient:
    def __init__(self, base_url):
        self.base_url = base_url

    def get(self, endpoint):
        try:
            response = requests.get(f"{self.base_url}{endpoint}")
            response.raise_for_status()
            return response.json()
        except requests.exceptions.ConnectionError:
            raise APIError(0, "接続できません")
        except requests.exceptions.Timeout:
            raise APIError(0, "タイムアウトしました")
        except requests.exceptions.HTTPError as e:
            raise APIError(e.response.status_code, str(e))

# 使用例
client = APIClient("https://api.example.com")
try:
    data = client.get("/users")
except APIError as e:
    print(f"APIエラー: {e}")

まとめ

  • try-exceptで例外をキャッチ
  • elseは例外が発生しなかった場合に実行
  • finallyは必ず実行される(クリーンアップに使用)
  • raiseで例外を発生させる
  • 具体的な例外をキャッチし、適切にログを取る
  • 独自例外でエラーの種類を明確にする

次回はモジュールとパッケージについて学びます。

広告エリア