こんにちは、Tech Samuraiです!
前回の記事「【Python中級編】もうエラーで止まらない!複数のエラーをまとめて処理する「エラーバンドリング」入門」では、複数のエラーをリストに集め、自作のカスタム例外でラップするという、非常に実用的なエラーハンドリング手法を探検しました。
あの手法は、Pythonの基本的な機能だけで実装できる素晴らしいものでした。しかし、「もし、この『エラーを束ねる』という仕組み自体が、Pythonの標準機能として、もっとエレガントに用意されていたとしたら…?」
実は、**Python 3.11**から、まさにそのための公式な武器、**`ExceptionGroup`**と**`except*`**(except star)という新しい構文が導入されたのです! 今回は、前回のコードをリファクタリングしながら、このモダンで強力なエラーハンドリングの「正解」をマスターしていきましょう。
おさらい:何が課題だったか?
前回の課題を思い出してみましょう。複数のタスクをループで処理する際、一つエラーが発生すると、そこで処理全体が止まってしまうのが問題でした。
# 以前の課題
# ID:3でエラーが出た瞬間に、ID:4以降の処理が実行されずに止まってしまう
for user_id in [2, 3, 4, 5, 6]:
process_user(user_id)
私たちは、これを解決するために、エラーをリストに溜め込み、最後にカスタム例外でラップして投げる、という手法を自作しました。
Python 3.11からの新しい英雄たち
この問題を、よりエレガントに解決してくれるのが、`ExceptionGroup`と`except*`です。
`ExceptionGroup`:例外を入れるための公式コンテナ
これは、複数の例外オブジェクトを内包できる、Pythonに組み込まれた新しい例外クラスです。私たちが前回自作した`DataProcessingError`の、いわば公式で超高機能なバージョンです。
`except*`:複数の例外を同時にキャッチする新しい構文
これが今回の主役です。`ExceptionGroup`の中から、**特定の型の例外だけをグループとして**キャッチすることができます。
例えるなら… 通常の`except`が「網で魚を一匹ずつ捕まえる」のだとすれば、`except*`は「投網を投げて、アジの群れ、サバの群れを、種類ごとにまとめて捕まえる」ようなイメージです。
try:
# ここでExceptionGroupが発生する
raise ExceptionGroup(
"問題発生",
[
ValueError("値が不正です"),
TypeError("型が違います"),
ValueError("別の値も不正です"),
]
)
# ExceptionGroupの中から、ValueErrorだけをまとめてキャッチ
except* ValueError as eg:
print(f"ValueErrorが {len(eg.exceptions)} 件見つかりました。")
for e in eg.exceptions:
print(f"- {e}")
# ExceptionGroupの中から、TypeErrorだけをまとめてキャッチ
except* TypeError as eg:
print(f"TypeErrorが {len(eg.exceptions)} 件見つかりました。")
このように、エラーの種類に応じて、後処理を分岐させることができるのです。
実践:前回のコードをリファクタリングする
それでは、前回の「エラー収集ロジック」を、この新しい構文を使って書き直してみましょう。
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)
# --- ここからが新しい! ---
# 1. もしエラーがあれば、公式のExceptionGroupとして投げる
if errors:
raise ExceptionGroup("いくつかのユーザー処理でエラーが発生しました", errors)
print("\n全ての処理が完了しました。")
そして、このコードを呼び出す側は、`except*`を使って以下のようにスマートにエラーを処理できます。
try:
# (上記のエラー収集コードをここに記述)
# ...
# 2. ExceptionGroupの中から、ValueErrorのグループだけをキャッチ
except* ValueError as eg:
print("\n--- 最終エラー報告 ---")
print(f"合計 {len(eg.exceptions)} 件の無効なIDエラーが検出されました。")
print("▼ 詳細:")
for error in eg.exceptions:
print(f" - {error}")
# もし他の種類のエラー(例: TypeError)があれば、ここに追加できる
# except* TypeError as eg:
# ...
実行結果:
ユーザーID: 2 の処理が成功しました。
ユーザーID: 4 の処理が成功しました。
ユーザーID: 6 の処理が成功しました。
--- 最終エラー報告 ---
合計 2 件の無効なIDエラーが検出されました。
▼ 詳細:
- ユーザーID: 3 は無効なIDです。
- ユーザーID: 5 は無効なIDです。
カスタム例外を自作した前回と、最終的な結果は同じです。しかし、Pythonに組み込まれた標準の仕組みを使うことで、より意図が明確で、他の開発者にも理解しやすいコードになりました。
まとめ
今回は、Python 3.11から導入された、モダンなエラーハンドリング機能について探検しました。
- 複数の例外を束ねるための公式な器、
ExceptionGroup
。 - その中から、特定の型の例外群をまとめてキャッチする、新しい構文
except*
。
特に、複数のネットワーク通信を並行して行う**非同期処理**などでは、複数の処理が同時に失敗することがよくあります。そのような場面で、この`ExceptionGroup`は真価を発揮します。
エラーハンドリングは、プログラムの堅牢性を決める重要な要素です。Pythonの進化に合わせて、私たちのコードも常にアップデートしていきましょう!
さて、少し専門的なエラー処理の世界を探検しました。次回は、がらりとテーマを変えて、ドローンを飛ばすまでの流れをシミュレーションしていきます!
コメント