例外処理したい(try...except...finally
/ raise
)
1try:
2 例外が発生するかもしれない処理
3except 例外の種類 as オブジェクト名:
4 例外が発生したときの処理
5except KeyboardInterrupt as e:
6 print("中断しました")
7 sys.exit(0) # Ctrl-cを補足して正常終了
8except Exception as e:
9 基本的な例外が発生したときの処理
10 print(f"Error: {e}")
11 print(f"{e.args=}")
12 sys.exit(1) # 想定外のエラーで異常終了
13finally:
14 例外の有無に関係なく実行する処理
try..except..finally
構文を使って例外処理(=エラーハンドリング)できます。
except
処理は複数回記述でき、finally
処理は省略できます。
例外の種類には
Pythonにビルトインの例外クラス名、もしくはカスタムした例外クラス名を指定します。
また、発生した例外をオブジェクト名で受け取っておくと、
例外をさらにコントロールできます。
オブジェクト名にはe
やerr
を使うことが多いです。
例外処理は、親クラスの名前でも補足できます。 利用可能なビルトイン例外とその階層構造の詳細は exception hierarchy を参照してください。
ヒント
1def main():
2 try:
3 ...
4 except Exception as e:
5 基本的な例外が発生したときの処理
6 except BaseException as e:
7 KeyboardInteruptなどExceptionで捕捉できない例外が発生したときの処理
例外処理は、基本的にmain
関数に実装します。
例外を送出したい(raise
)
1if 条件確認:
2 条件が正しいときの処理
3else:
4 raise 例外クラス名
raise
を例外を送出(=throw)できます。
例外クラス名は、ビルトインの例外クラスが使えます。
また、カスタムした例外クラスも作成できます。
自作パッケージを作成している場合、 適切な例外を発生させることで、ユーザー(自分自身を含む)に 優しいパッケージになります。
raise
or sys.exit
例外(=エラー)が発生するとき、
raise
するか、
sys.exit
するかを考える必要があります。
一般的に、
ライブラリ(やサブ関数)の中ではraise
し、
CLI(やメイン関数)で例外をキャッチしてsys.exit
すればよさそうです。
インポートして利用するパッケージの場合、
パッケージ内部でsys.exit
されていると、
パッケージのユーザーはそこから先、どうすることもできません。
しかし、適切な例外がraise
されていれば、
その例外処理をどうすべきか(キャッチして対処するのか、そのまま握り潰すのか、など)
ユーザーが判断できます。
1def func1():
2 """サブ関数: 条件にマッチしない場合 ValueErrorを送出
3 """
4 if 条件:
5 条件が正しいときの処理
6 return
7 else:
8 # ここではsys.exitせずにraiseする
9 msg = "値がおかしいよ"
10 raise ValueError(msg)
11
12def func2():
13 ...
14 if 条件:
15 ...
16 else:
17 msg = "接続失敗"
18 raise ConnectionError(msg)
19
20def main():
21 try:
22 func1()
23 func2()
24 except ValueError as e:
25 print(f"Error: {e}")
26 print(f"{e.args=}")
27 sys.exit()
28 except ConnectionError as e:
29 print(f"Error: {e}")
30 print(f"{e.args=}")
31 sys.exit()
32 except KeyboardInterrupt as e:
33 print(f"Error: {e}")
34 print(f"{e.args=}")
35 sys.exit()
36
37if __name__ == "__main__":
38 main()
このサンプルは、僕がスクリプトやパッケージを作成するときのテンプレ的なものです。
raise
された例外は親の関数にも伝播します。
インポートした外部パッケージで例外が適切に送出されている場合、
サブ関数の中ではtry...except
せずにそのままにしておけばOKです。
よく遭遇する例外と確認事項
例外が発生した場合、まずエラーメッセージをきちんと確認することが大切です。 どの例外が、どのファイル(のどのあたり)で発生したかを把握することで、 その理由を考えたり、対応方法を考えたりできます。
以下に遭遇しがちなビルトイン例外と、そのときの確認事項を整理してみました。
- ImportError / ModuleNotFoundError:
モジュールのインポートが正しくない場合に発生します。 モジュール名が正しいか、モジュールがインストールされているか、Python実行環境のパスが正しいか、確認しましょう。
- AttributeError / KeyError / IndexError / ValueError:
オブジェクト操作が正しくない場合に発生します。 リストの長さをオーバーしていないか、辞書型にないキーを指定していないか、確認しましょう。
- NameError:
変数名が正しくないときに発生します。 変数の定義を忘れていないか、変数名にスペルミスがないか、確認しましょう。
- SyntaxError / IndentationError:
文法や構文が正しくないときに発生します。
インデントがずれていないか、条件文の末尾の:
を忘れていないか、確認しましょう。
- FileExistsError / FileNotFoundError:
ファイル操作が正しくないときに発生します。 ファイル名が正しいか、既存のファイルが存在していないか、確認しましょう。
中断処理したい(KeyboardInterrupt
)
1try:
2 例外が発生するかもしれない処理
3except KeyboardInterrupt as e:
4 中断処理
5 # バッファーの内容をファイルに保存する、など
6 print(f"Error: {e}")
7 sys.exit()
KeyboardInterrupt
クラスで、中断処理(ctrl-c)したときの例外処理ができます。
中断処理すると、そのときバッファーにあるデータなどは破棄されます。
長時間走らせるようなスクリプトや、CLIツールには必ず中断処理を書いて、
安全に終了できるようにしましょう。
パッケージの例外処理したい
1try:
2 response = requests.get("https://httpbin.org/status/404")
3 response.raise_for_status()
4except requests.exception.RequestException as e:
5 print(f"Error: {e}")
外部パッケージによっては、パッケージ独自の例外クラスがあります。 上記サンプルは、requestsパッケージの例外処理です。 このように、パッケージによっては、カスタムの例外クラスが定義されてます。 詳細はパッケージのドキュメントを確認してください。
例外クラスをカスタムしたい(Exception
)
1# MODULE_NAME.py
2class CustomError(Exception):
3 """CustomError: 最小構成(何もしない)"""
4 pass
5
6def CustomFunction(...):
7 if 条件:
8 正常処理
9 else:
10 raise CustomError("エラー発生!"):
Exception
クラスを継承して、例外クラスをカスタムできます。
小規模なスクリプトでは、ビルトインの例外クラスで十分対応できると思うので、
わざわざ例外クラスを自作する必要はありません。
上記のサンプルは、例外クラス名だけを変更しています。 公開されている有名なパッケージでもよく使われている気がします。
1# exceptions.py
2class CustomError(Exception):
3 """CustomError: 自作の例外クラス"""
4 def __init__(self,
5 message: str = "デフォルトのエラーメッセージ",
6 error_code: int | None = None
7 ):
8 """CustomErrorを初期化
9
10 Args:
11 message (str): エラーメッセージ
12 error_code (int): エラーコード
13 """
14 super().__init__(message) # 親クラスのExceptionクラスの初期化
15 self.message = message
16 self.error_code = error_code
17
18 def __str__(self):
19 """例外の文字列表現
20
21 Returns:
22 str: [エラーコード] エラーメッセージ
23 """
24 if self.error_code is not None:
25 return f"{[{self.error_code}] {self.message}}"
26 return self.message
以下のようなユースケースで、自作を検討したほうがいいかもしれません。
特定の例外シナリオに対処したい
例外処理をカプセル化したい
他の例外と区別したい
中規模〜大規模なパッケージを開発している場合には、複数の例外クラスが必要になるかもしれません。
その場合は、exceptions.py
のようなファイルに整理するのが一般的なようです。
カスタムした例外クラスの作成は@dataclass
デコレーターを使って簡略化できます。
1# exceptions.py
2from dataclasses import dataclass
3
4@dataclass
5class CustomError(Exception):
6 """CustomError: 自作の例外クラス
7
8 dataclassを使って簡略化
9 """
10 message: str = "デフォルトのエラーメッセージ"
11 error_code: int | None = None
12
13 def __str__(self):
14 """例外の文字列表現"""
15 if self.error_code is not None:
16 return f"[{self.error_code}] {self.message}"
17 return self.message
1from .exceptions import CustomError
2
3def custom_function(...):
4 if 条件:
5 正常処理
6 else:
7 raise CustomError("エラー発生!", 101)
8
9try:
10 custom_function(...)
11except CustomError as e:
12 print(e)
13 # => "[101] エラー発生!"