実は知らないPythonのraise
Pythonのraiseといえば当然書いててよく出てくるんですが・・・
これに限らず基本的な文法って基本的すぎて実はドキュメントの詳細を読んだことがない人が多かったりして、自分も含めて詳細を知らないことが多いんですよね。
なんで今回はふと書いてて疑問に思ったraiseの仕様の詳細を紹介していこうと思います。
raise
Pythonでraiseするとき、通常はおそらくこんな感じで書いてるはずですが・・・
raise ValueError()
実はこうかけます。
raise ValueError
raiseはインスタンスじゃなくていい
そうです。実はPythonのドキュメントではクラスかインスタンスを指定する必要があると書かれています。
なので引数が必要ない場合は実はインスタンス化する必要はありません。
これは例外インスタンスか例外クラス(BaseException を継承したクラス、たとえば Exception やそのサブクラス)でなければなりません。 例外クラスが渡された場合は、引数無しのコンストラクタが呼び出され、暗黙的にインスタンス化されます
周りにも聞いてみたんですが、かなりPythonを長く書いてるエンジニアも以外と知っている人が少ないです。
raise ... from ...
実はraiseでもfrom構文が使えます。
具体的にいつ使うのかというと、例外処理中に別の例外を送出する場合です。
def process_data(data):
try:
result = int(data)
except ValueError as e:
raise TypeError("int型に変換することは出来ません") from e
try:
process_data("abc")
except TypeError as e:
print(f"例外が発生しました: {e}")
print(f"元の例外: {e.__cause__}")
あまりこういったケースでfromをきっちり使っていることはないですが、本来はraise from構文を使用するのが推奨されます。
通常のraiseとの違い
結論から言うと、エラー時のメッセージが異なります。
はい、おそらくそれだけです・・・。
ただあえてメリットを挙げるなら、このメッセージの違いでその例外が意図的に送出されたものなのかが判断しやすくなります。
ValueError: invalid literal for int() with base 10: 'test'
During handling of the above exception, another exception occurred:
ValueError: invalid literal for int() with base 10: 'test'
The above exception was the direct cause of the following exception:
詳細
さてこの挙動の違いですが、全ての例外の基底クラスであるBaseExceptionクラスにはいくつかの属性がありますが、例外取り扱い時には主に__cause__
, __context__
, __suppress_context__
の3つが参照されます。
このうち__cause__
はその例外の直接の原因である例外が、__context__
にはその例外が発生するまでの元の例外が保存されます。
そしてraise from構文を使用すると__cause__
に元の例外が保存された上で__suppress_context__
がTreu
に設定されます。
そして__context__
が参照されるのは__cause__
がNoneかつ__suppress_context__
がFalse
の場合なので、raise fromを使うと__cause__
の情報が表示されることになります。
def convert(s: str):
try:
return int(s)
except ValueError as e:
raise TypeError from e
try:
convert("test")
except TypeError as e:
print(e.__cause__)
print(e.__context__)
print(e.__suppress_context__)
# raise fromの場合
# invalid literal for int() with base 10: 'test'
# invalid literal for int() with base 10: 'test'
# True
# 通常のraiseの場合
# None
# invalid literal for int() with base 10: 'test'
# False
raise from None
ドキュメントにもありますが、特殊な使い方としてraise ... from None
とすることで完全に新しい例外として置き換え、元の例外のトレースバックを非表示にすることも可能です。
もちろんその場合も__context__
には自動的に元の例外が保存されているため、デバッグすることは出来ます。
Discussion