Python 例外処理 「Exceptionを多用すべきでない」はわかるんだけど
例外処理苦手です。
例外処理設計・・・一番悩むかも・・・
Exceptionで使えば全部拾えるじゃん!
楽ちんじゃん!
ってそんな簡単な話じゃないから困る。
1. Exception を多用すべきでない理由
Exception はすべての例外の親クラスであり、予期しないエラーまで捕まえてしまう 可能性があるため、以下のような問題が・・・
問題点
予期しない例外までキャッチする
Exception をキャッチすると、例えば KeyboardInterrupt や SystemExit など、プログラムの正常な終了を妨げる例外まで拾ってしまう。
デバッグが困難になる
具体的なエラーの種類が分からず、エラーログを見ても原因特定が難しくなる。
例えば FileNotFoundError なのか PermissionError なのか分からない。
バグを隠してしまう
例外の種類に応じた適切な処理をせず、とりあえず pass や print で済ませると、プログラムが異常な状態のまま動き続けてしまう。
2. じゃあどうすればいいのか?
(1) 発生する例外を特定し、個別にキャッチする
すべての例外を調べるのは確かに面倒ですが、主なエラーを予測し、それに応じたハンドリングをするのが基本とのこと
例えば、ファイル操作をする場合:
def read_file(filename):
try:
with open(filename, 'r') as f:
return f.read()
except FileNotFoundError:
print("ファイルが見つかりません")
return None
except PermissionError:
print("ファイルにアクセスする権限がありません")
return None
Exceptionは最後の砦として 使う
とはいえ、どうしても未知の例外 をキャッチする必要がある場合、最後の手段として Exception を使うこともあります。
ただし、以下のように ログに記録したり、エラーを再送出する形 にするのが推奨されます。
import logging
def process_data(data):
try:
# ここで何か処理
result = 10 / data # 例外が発生する可能性あり
return result
except ZeroDivisionError:
print("ゼロで割ることはできません")
return None
except Exception as e:
logging.error(f"予期しないエラーが発生: {e}", exc_info=True)
raise # 例外を再送出
#exc_info=True は、logging.error() で 例外の詳細な情報(スタックトレース)をログに出力するためのオプション です。
ポイント:
Exception をキャッチする場合でも、logging.error() で詳細なエラーログを記録
raise で例外を再送出し、上位で適切に処理できるようにする(握りつぶさない)
っていうのはよくある話。ここでちょっと疑問
例外全てを把握して全部個別に書くの?
今回みたいにファイル操作だったら4つ程度で済むかもしれない。
でも何かしらのメソッドで20個くらいの例外パターンとかあったらどうするんだろう・・・全部調べるのキツくない?ってなった。
上位クラスを使ってある程度まとめてしまう。
try:
with open("file.txt", "r") as f:
data = f.read()
except OSError as e: # その他のI/Oエラー
print(f"OS関連のエラー: {e}")上記は極端すぎるが、OSError は FileNotFoundError や PermissionError の親クラスなので、細かく分けるのが大変な場合、より上位のクラスをキャッチするのも一つの方法としていいのではないかと思った。
クラス関係は以下
BaseException
├── Exception
├── OSError
├── FileNotFoundError
├── PermissionError
├── IsADirectoryError
でもまた疑問で上位クラスを調べる手間が・・・
1. 組み込み例外の上位クラスを調べる方法
Pythonの組み込み例外の 継承関係 は、次の方法で確認できます。
(1) __mro__(Method Resolution Order)を使う
__mro__ を使うと、そのクラスの 親クラスの順番 が確認できる。
print(FileNotFoundError.__mro__)
(<class 'FileNotFoundError'>, <class 'OSError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)
FileNotFoundError は OSError → Exception → BaseException の順に継承されているっていうのがわかる。
これを使えば、「この例外はどこまでまとめてキャッチできるのか?」がわかって便利そうだ。知らなかった。
外部ライブラリとかはdir() を使って例外一覧を確認
ライブラリがどんな例外を持っているか調べるときは、dir() を使うのが手っ取り早そう。
たとえばrequests
import requests
print([attr for attr in dir(requests.exceptions) if "Error" in attr or "Exception" in attr])
['ChunkedEncodingError', 'ConnectionError', 'ConnectTimeout', 'HTTPError',
'InvalidHeader', 'InvalidJSONError', 'InvalidSchema', 'InvalidURL',
'MissingSchema', 'ProxyError', 'ReadTimeout', 'RequestException', 'SSLError',
'StreamConsumedError', 'Timeout', 'TooManyRedirects', 'URLRequired']
とりあえず例外が多そうだったら、ある程度カテゴリ化してまとめた方がよさそう。
例えば OSError で I/O 関連のエラーをまとめる など
あと例外を自分で作るカスタム例外とか使ってまとめてみるのもいいかも。
例外処理は難しい
