Pythonのサブクラスを活用したエラーハンドリングの実装

# はじめに
Pythonのエラーハンドリングで `try ~ except Exception as e` のような処理も書けますが、サブクラスを使用する事でより細かなエラーハンドリングをすることができます。本記事ではこれらを使用したエラーハンドリングについて簡単な例を実装していきます。

# サブクラスを使用するメリット
- エラーの種類ごとに異なる処理が可能
- 各エラーに対してエラーメッセージをより具体的に設定できる
- エラーの階層構造を作れる

# 具体例
デフォルトの Exception と サブクラス を使用したものを１つずつ見てみます。

## 例1：Exception クラス
```python
def connect_database():
    try:
        # データベース接続の処理
        connection = db.connect()
    except Exception as e:
        # すべてのエラーを一括で処理
        print(f"エラーが発生しました: {e}")
        return None
```

上記の場合、エラーメッセージ（＝ `e` ）から何が原因なのかを読み取ることはできますが、エラーの種類ごとに異なる処理を書くことができません。
例えば「接続エラーの場合は再接続を試みる」「タイムアウトの場合は少し待ってから再接続する」といった処理の振り分けができません。

## 例2：サブクラス
```python
def connect_database():
    try:
        # データベース接続の処理
        connection = db.connect()
        
    except ConnectionError as e:
        # 接続エラーの場合の処理
        print(f"接続エラー: {e}")
        # 再接続を試みる
        return retry_connection()
        
    except TimeoutError as e:
        # タイムアウトの場合の処理
        print(f"タイムアウト: {e}")
        # 時間をおいて再接続
        return wait_and_retry()
        
    except Exception as e:
        # 想定外のエラーの処理
        print(f"予期せぬエラーが発生しました: {e}")
        return None
```

サブクラスを使用するとエラーの種類によって異なる対処が可能です。
例えば、接続エラーの場合はすぐに再接続を試み、タイムアウトの場合は少し時間をおいてから再試行するなど、状況に応じた適切な処理を実装できます。

# サブクラスを使用した処理の実行例

例としてファイル読み込みの処理をクラスとして定義し、サブクラスを使用した処理を実行していきます。

```python:index.py
def read_file(filepath):
    # ファイルの読み込みを行う関数
    with open(filepath, 'r') as f:
        return f.read()

def try_read(filepath):
    try:
        # ファイルを読み込む
        content = read_file(filepath)
        print(f"ファイルの内容: {content}")
        
    except FileNotFoundError as e:
        # ファイルが存在しない場合の処理
        print(f"ファイルが見つかりません: {e}")
        
    except TypeError as e:
        # ファイルパスの型が不正な場合の処理
        print(f"ファイルパスの型が不正です: {e}")
        
    except Exception as e:
        # その他の予期せぬエラーの処理
        print(f"予期せぬエラー: {e}")

# test.txtファイルは事前に作成されてる想定
print("1. 正常な実行:")
try_read("test.txt")

print("\n2. 存在しないファイルの読み込み:")
try_read("not_exist.txt")

print("\n3. 不正な型のファイルパス:")
try_read(None)
```

## read_file, try_read について
1. `read_file`関数について
   - `read_file`メソッド：与えられたファイルパスのファイルを読み込む
     - ファイルが存在する場合：内容を返す
     - ファイルが存在しない場合：FileNotFoundErrorを発生
     - パスの型が不正な場合：TypeErrorを発生

2. `try_read`関数について
   - read_file関数のインスタンスを作成
   - ファイル読み込みを試みて、発生するエラーの種類に応じて処理を分岐
   - エラーメッセージを出力

## スクリプトを実行

次のコマンドで処理を実行してみます。

```
python .\index.py
```

:::note warn

スクリプトを実行する前に、カレントディレクトリに  `test.txt` が準備されている事を想定しています。
本記事では `Hello, World!` と書き込んだものを使用しています。

:::


```
1. 正常な実行:
ファイルの内容: Hello, World!

2. 存在しないファイルの読み込み:
ファイルが見つかりません: [Errno 2] No such file or directory: 'not_exist.txt'

3. 不正な型のファイルパス:
ファイルパスの型が不正です: expected str, bytes or os.PathLike object, not NoneType
```

各ケースの動作を詳しく見てみましょう：

1. 正常系（test.txt）
   - 実在するファイルなので読み込み成功
   - ファイルの内容（`Hello, World!`）が表示される

2. 存在しないファイル（not_exist.txt）
   - ファイルが存在しないので`FileNotFoundError`が発生
   - エラーをキャッチして適切なメッセージ（`ファイルが見つかりません`）を表示

3. 不正な型のパス（None）
   - 文字列以外のパスなので`TypeError`が発生
   - エラーをキャッチして適切なメッセージ（`ファイルパスの型が不正です`）を表示

このように、エラーの種類に応じて異なるメッセージを表示し、適切な処理を行うことができます。


## 参考
https://docs.python.org/ja/3/library/exceptions.html#FileNotFoundError
https://docs.python.org/ja/3/library/exceptions.html#TypeError
https://docs.python.org/ja/3/library/exceptions.html#Exception