はじめに
こんにちは。技術4課の保田(ほだ)です。
iOS14 へのアップデートをしたらかなり雰囲気が変わってビックリしています。
というわけで(?)今日は Python のお話です。
要約
raise 文には from が使えるぞ
本題
main 関数から呼び出されるある関数があったとして、次のような例外処理をしたいとします。
特定の例外クラス以外は全部一つの例外クラスでまとめた上で、改めて main 関数でキャッチしたい!
ややこしいので例を挙げます。
import traceback def main(): try: target() print('success') except Exception: print(traceback.format_exc()) def target(): try: 1/0 # ZeroDivisionError が起きる except IndexError: raise except Exception as err: raise Exception("IndexError 以外の何かしらのエラーが発生しました") if __name__ == '__main__': main()
main 関数から target 関数を呼び出しています。 どちらの関数も try-except 構文を使い、 target 関数の方は発生した例外は main 関数に投げるようになっています。
この target 関数は 1/0
(つまりゼロ除算)があるので明らかに ZeroDivisionError
しか発生しないことが分かります。
が、あくまでサンプルなので、もう少しいろいろな例外が発生しうる処理があると想像してください。
その中で、 IndexError
が発生したときだけそのまま IndexError
として main 関数に投げるようになっています。
まず、この 1/0
を [][0]
に変えて実行してみます。
$ python sample.py Traceback (most recent call last): File "sample.py", line 5, in main target() File "sample.py", line 15, in target [][0] # IndexError IndexError: list index out of range
IndexError
として出力されていますね。
main 関数の except 節内の print(traceback.format_exc())
によってスタックトレースが文字列形式で出力されます。
では [][0]
を 1/0
に戻してまた実行してみます。
$ python sample.py Traceback (most recent call last): File "sample.py", line 14, in target 1/0 # ZeroDivisionError ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "sample.py", line 5, in main target() File "sample.py", line 18, in target raise Exception("IndexError 以外の何かしらのエラーが発生しました") Exception: 何かしらのエラーが発生しました
ちょっとイカつい感じになりました。 特に気になるのがこの一文です。
During handling of the above exception, another exception occurred:
日本語に訳すと「上記の例外(ゼロ除算)の処理中に別の例外が発生しました」となります。
なんだか意図しないエラーが起きてしまったような気がして心穏やかではないですね。
もうちょっとイイ感じにしてみましょう。
といっても実は 例外クラスのドキュメント を見ると冒頭にガッツリ書いてあります。
現在処理中の例外を raise を使って再送出するのではなく新規に例外を送出する場合、raise と一緒に from を使うことで暗黙の例外コンテキストを捕捉することができます
これだけ読むと何言ってるのかようわからんという感じですので、さっそく試してみましょう。
def target(): try: 1/0 # ZeroDivisionError が起きる except IndexError: raise except Exception as err: raise Exception("IndexError 以外の何かしらのエラーが発生しました") from err
raise 文の末尾に from err
を追加しました。
$ python sample.py Traceback (most recent call last): File "sample.py", line 14, in target 1/0 # ZeroDivisionError ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "sample.py", line 5, in main target() File "sample.py", line 20, in target raise Exception("IndexError 以外の何かしらのエラーが発生しました") from err Exception: IndexError 以外の何かしらのエラーが発生しました
先ほど気になっていた一文がちょっとだけ変わりました。
The above exception was the direct cause of the following exception:
「上記の例外(ゼロ除算)は、次の例外の直接の原因です」といった感じでしょうか。
これだと最終的に出力されるエラーメッセージ「何かしらのエラーが発生しました」(私が勝手に決めたものですが)の直接の原因はゼロ除算なので、対応すべきは ZeroDivisionError
だな、というのが分かりやすいので良いですね。
また、 先ほどのドキュメント の続きには次の記述があります。
from に続く式は例外か None でなくてはなりません。
None
も使えるようです。試してみましょう。
def target(): try: 1/0 # ZeroDivisionError が起きる except IndexError: raise except Exception as err: raise Exception("IndexError 以外の何かしらのエラーが発生しました") from None
実行してみるとこうなります。
$ python sample.py Traceback (most recent call last): File "sample.py", line 5, in main target() File "sample.py", line 19, in target raise Exception("IndexError 以外の何かしらのエラーが発生しました") from None Exception: IndexError 以外の何かしらのエラーが発生しました
実際のエラーの詳細が Exception: 何かしらのエラーが発生しました
に丸め込まれています。
何かしらのライブラリを作る際に、細かいエラーを丸め込んで一つのユーザー定義のエラーとして呼び出し元に返したいときはこちらの方が良いかもしれません。
まとめ
エラーメッセージをイイ感じにしたい際は参考にしていただければと思います。