【Python中級編】もうエラーで止まらない!複数のエラーをまとめて処理する「エラーバンドリング」入門

ALL

こんにちは、Tech Samuraiです!
Pythonプログラミングにおいて、try-exceptを使ったエラーハンドリングは基本中の基本です。しかし、プログラムが複雑になってくると、単純なtry-exceptだけでは対応しきれない場面が出てきます。

例えば、「たくさんのファイルを一括で処理するスクリプトで、いくつかのファイルでエラーが出ても、処理全体は止めずに最後まで実行し、最後にまとめてエラー報告をしたい」といったケースです。

今回のテーマは、このような要件を実現するための高度なエラーハンドリング手法、**「エラーバンドリング(Error Bundling)」**の考え方です。複数のエラーを一つの束(バンドル)としてまとめて扱うことで、より堅牢で親切なプログラムを設計する方法を探検しましょう!


問題点:単純な`try-except`の限界

まず、従来の方法の限界を見てみましょう。例えば、複数のユーザーIDを処理するループがあったとします。

def process_user(user_id):
    if user_id % 2 == 0:
        # 偶数IDは正常に処理
        print(f"ユーザーID: {user_id} の処理が成功しました。")
    else:
        # 奇数IDはエラーを発生させると仮定
        raise ValueError(f"ユーザーID: {user_id} は無効なIDです。")

user_ids = [2, 3, 4, 5, 6]

try:
    for user_id in user_ids:
        process_user(user_id)
except ValueError as e:
    print(f"\nエラーが発生したため、処理を中断しました: {e}")

実行結果:

ユーザーID: 2 の処理が成功しました。

エラーが発生したため、処理を中断しました: ユーザーID: 3 は無効なIDです。

IDが`3`の時点でエラーが発生し、ループ全体が中断してしまいました。本当は、`4`と`6`は正常に処理できるはずなのに、もったいないですよね。


解決策:エラーを収集し、最後にまとめて報告する

エラーバンドリングの基本的な戦略はシンプルです。

  1. ループの前に、発生したエラーを溜めておくための空のリスト(`errors`)を用意する。
  2. ループの中の処理を個別にtry-exceptで囲む。
  3. エラーが発生してもループは止めず、エラー情報を`errors`リストに追加する。
  4. 全てのループが終わった後、`errors`リストが空でなければ、溜まったエラーをまとめて報告する。

さらにプロフェッショナルな方法として、複数のエラーを格納できる**「カスタム例外クラス」**を自作してみましょう。


実装:カスタム例外を使ったエラーバンドリング

1. 複数のエラーを格納できる、独自の例外クラスを定義

# Python標準のExceptionクラスを継承して、独自の例外クラスを作成
class DataProcessingError(Exception):
    def __init__(self, message, errors):
        super().__init__(message)
        self.errors = errors # 発生したエラーのリストを格納する

2. エラー収集ロジックの実装

def process_user(user_id):
    if user_id % 2 == 0:
        print(f"ユーザーID: {user_id} の処理が成功しました。")
    else:
        raise ValueError(f"ユーザーID: {user_id} は無効なIDです。")

user_ids = [2, 3, 4, 5, 6]
errors = [] # エラーを収集するリスト

# --- メインの処理ループ ---
for user_id in user_ids:
    try:
        process_user(user_id)
    except ValueError as e:
        # ループは止めず、エラー情報をリストに追加
        errors.append(e)

# --- ループ終了後の最終報告 ---
if errors:
    # 収集したエラーを、自作の例外クラスにまとめて投げる
    raise DataProcessingError("いくつかの処理でエラーが発生しました。", errors)

print("\n全ての処理が完了しました。")

3. 実行と結果の確認

このスクリプト全体を、さらに大きなtry-exceptブロックで囲んで実行してみましょう。

# (上記のコード全体をここに記述)
try:
    # (メインの処理ループをここに記述)
    # ...
    if errors:
        raise DataProcessingError("いくつかの処理でエラーが発生しました。", errors)
    print("\n全ての処理が完了しました。")

except DataProcessingError as e:
    print(f"\n--- 最終エラー報告 ---")
    print(e) # メインのメッセージ
    print("▼ 詳細:")
    for i, error in enumerate(e.errors):
        print(f"  {i+1}: {error}")

実行結果:

ユーザーID: 2 の処理が成功しました。
ユーザーID: 4 の処理が成功しました。
ユーザーID: 6 の処理が成功しました。

--- 最終エラー報告 ---
いくつかの処理でエラーが発生しました。
▼ 詳細:
  1: ユーザーID: 3 は無効なIDです。
  2: ユーザーID: 5 は無効なIDです。

見事に、成功した処理は全て実行され、失敗した処理のエラーだけが最後にまとめて報告されました!


まとめ

今回は、単純なエラー処理から一歩進んだ、より堅牢な「エラーバンドリング」という考え方を探検しました。

  • エラーが発生しても処理を止めず、**エラー情報をリストに収集**する。
  • **カスタム例外クラス**を定義し、複数のエラーを一つの例外としてまとめる。
  • これにより、プログラムの**可用性(動き続ける能力)**と**診断のしやすさ**が格段に向上する。

この手法は、大量のデータを扱うバッチ処理や、複数のAPIと通信するプログラムなど、失敗が許容される一方で、全体を止めたくない場面で非常に役立ちます。

さて、少し専門的なエラー処理の世界を探検しました。次回は、エラーバンドリングについて更にう深堀していきます!

コメント

タイトルとURLをコピーしました