はじめに
RAISE THE DREAM!!! nikkieです。
ミリアニ12話を思い出すだけで泣ける身体にされてしまった1のですが、今回は技術ネタ。
Pythonでraise
するものといったら、例外!2
Pythonの例外の扱い、具体的に言うとtry ... exceptのexcept節について取り上げます。
目次
- はじめに
- 目次
- きっかけ:『Python実践レシピ』を読んでいて
- 結論:except節の書き方
- BaseExceptionとExceptionは何が違うの?
- except節と例外の継承関係
- PEP 8の「Programming Recommendations」より
- 『Python Distilled』より
- 終わりに
きっかけ:『Python実践レシピ』を読んでいて
3章が言語仕様を扱う章で、3.1が例外処理です。
「3.1.6 例外処理:よくあるエラーと対処法」の中に
また、except節で例外の種類を指定しなかったり、except Exception:のように基底クラスを指定してしまうのも推奨されません。(Kindle版 p.128)
とあります。
私は「あれ、そうだったっけ?」と思ったのです
結論:except節の書き方
- オススメしない書き方(ぶっぶーですわ🙅♂️)
except:
と例外の種類を指定しないexcept BaseException:
(すべての例外の基底クラスを指定)- この2つが同じことはPEP 8にあります
- オススメする書き方
BaseExceptionとExceptionは何が違うの?
まずBaseExceptionとExceptionの区別です。
「組み込み例外」のドキュメントより
「組み込み例外」のドキュメントの「例外のクラス階層」より
https://docs.python.org/ja/3/library/exceptions.html#exception-hierarchy
- BaseExceptionがすべての例外の基底クラス
- ExceptionはBaseExceptionを継承している
- BaseExceptionを継承しているのはExceptionだけではない
- KeyboardInterrupt
- Ctrl+Cで送出されている
- SystemExit
sys.exit
で送出される
- KeyboardInterrupt
同じドキュメントの「基底クラス」も見ましょう。
https://docs.python.org/ja/3/library/exceptions.html#base-classes
- BaseException
全ての組み込み例外の基底クラスです。
ユーザ定義の例外に直接継承されることは意図されていません (継承には Exception を使ってください)。
- Exception
システム終了以外の全ての組み込み例外はこのクラスから派生しています。
全てのユーザ定義例外もこのクラスから派生させるべきです。
BaseExceptionとExceptionの違い
- BaseExceptionは、全ての組み込み例外の基底クラス
- 子クラスとして、Exception、KeyboardInterrupt、SystemExit(など)
- Exceptionはユーザ定義例外の基底クラス
- Exceptionを継承した組み込み例外もある
except節と例外の継承関係
Pythonチュートリアルの「8.3. 例外を処理する」を参照します。
https://docs.python.org/ja/3/tutorial/errors.html#handling-exceptions
except 節のクラスは、例外と同じクラスか基底クラスのときに互換 (compatible)となります。
Pythonチュートリアルのサンプルコード
- BはExceptionを継承したクラス
- CはBを継承(もちろんExceptionも継承している)
for cls in [B, C]: try: raise cls() except C: print("C") except B: print("B")
B C
- BのインスタンスはCではないので、2つ目のexcept節で処理
>>> isinstance(B(), C) False
- Cは1つ目のexcept節で処理
except節を入れ替えると
for cls in [B, C]: try: raise cls() except B: print("B") except C: print("C")
B B
- Bは1つ目のexcept節で処理
- CのインスタンスはBなので、Cも1つ目のexcept節で処理
- Bが基底クラスという継承関係!
>>> isinstance(C(), B) True
BaseExceptionとExceptionに読み替えると
except Exception:
はBaseExceptionを捕捉しない- BaseExceptionを継承しているが、Exceptionを継承していないKeyboardInterruptやSystemExitを捕捉しない
except BaseException:
- BaseExceptionを継承しているExceptionを捕捉する
- BaseExceptionを継承しているKeyboardInterruptやSystemExitも捕捉する
except BaseException:
と書くと、KeyboardInterruptやSystemExitも捕捉してしまうのが望ましくない挙動と考えます。
なぜならこれらのドキュメントには
Exception をキャッチするコードに誤ってキャッチされないように、Exception ではなく BaseException を継承しています。(SystemExit)
のように書いてあるからです。
PEP 8の「Programming Recommendations」より
https://peps.python.org/pep-0008/#programming-recommendations
例外に関してもいくつか記載されています
Derive exceptions from Exception rather than BaseException.
ユーザ定義例外は、BaseExceptionではなくExceptionを継承しようという話ですね。
A bare
except:
clause will catch SystemExit and KeyboardInterrupt exceptions, (略)
例外のクラスを指定しないexcept:
をbare(むき出しの)と言っています。
bare except is equivalent to
except BaseException:
bare exceptはexcept BaseException:
と同じなので、SystemExitやKeyboardInterruptを捕捉する
If you want to catch all exceptions that signal program errors, use
except Exception:
プログラムのエラーを合図するすべての例外を捕捉したいときはexcept Exception:
を使おう、とあります。
PEP 8にあるので、flake8も検出してくれるみたいですね
『Python Distilled』より
3.4で例外を扱っています。
分かりやすいなと思ったのは、以下の説明
- プログラムに関連するエラーはExceptionを継承
- (感想)ValueError(など)や、ユーザ定義例外のことを「プログラムに関連するエラー」と呼ぶのうまい
- 制御フローの変更に使われる例外
- SystemExitやKeyboardInterruptなど(3.4.2)
3.4.6にアドバイスがいくつかあります
エラーを捕捉する際は、except節で捕捉する例外の範囲を限定しましょう。
except Exceptionと書いてしまうと、無視できない正当なプログラミングエラーも捕捉することになります。デバッグが難しくなるので、これはやめておきましょう。
これを受けての私の意見ですが、
- 『Python Distilled』の主張には賛成。例外の範囲を限定したい
- 現実的には、どんな例外が送出されるか分かりきっておらず、範囲を限定できないケースもあると思う。そんなときに限っては
except Exception:
と書くのもOKとしたい- 限定できたら書き直そう
- 似ているからと言って、
except BaseException:
と書いてはいけません(ここまで見てきたとおりです)
終わりに
Pythonのexceptの書き方を見てきました。
except節に書くクラスは理解がふんわりしていたのですが、ドキュメントや『Python Distilled』にあたって、BaseExceptionはよくなくてExceptionはよいということがなぜかまで含めて理解できました🙌
- ありがとう😭 ↩
-
↩#ミリアニネタバレ感想
— nikkie / にっきー (@ftnext) 2023年11月3日
raiseするもの
パイソン「例外(Exception, Error)」
ミリオン「ドリーム!!!」