※当サイトの記事には、広告・プロモーションが含まれます。

Pythonのtry exceptが複数の時に例外の補足箇所には注意が必要な話

www.itmedia.co.jp

 AIがサポートした“ビートルズの最後の新曲”、「Now And Then」が2月3日(現地時間)、最優秀ロックパフォーマンス部門のグラミー賞を受賞した。AIが編集に使われた楽曲がグラミー賞を受賞したのはこれが初だ。

AIがサポートしたビートルズ楽曲、グラミー賞受賞 - ITmedia NEWS

⇧ 曲自体が評価されたのか、AI導入により復元技術が評価されたのか、気になるところですな...

Pythonの標準で用意されている例外

公式のドキュメントを見た感じでは、

docs.python.org

組み込み例外

Python において、すべての例外は BaseException から派生したクラスのインスタンスでなければなりません。

https://docs.python.org/ja/3.13/library/exceptions.html

基底クラス

exception BaseException

全ての組み込み例外の基底クラスです。ユーザ定義の例外に直接継承されることは意図されていません (継承には Exception を使ってください)。このクラスのインスタンスに str() が呼ばれた場合、インスタンスへの引数の表現か、引数が無い場合には空文字列が返されます。

https://docs.python.org/ja/3.13/library/exceptions.html

exception Exception

システム終了以外の全ての組み込み例外はこのクラスから派生しています。全てのユーザ定義例外もこのクラスから派生させるべきです。

https://docs.python.org/ja/3.13/library/exceptions.html

⇧ ユーザーが独自の例外を定義する場合は「Exception」クラスを継承するのであって、「BaseException」クラスではありませんと。

一応、Python 3.13における「例外クラス」の一覧は、以下が全量ということらしい。

■例外のクラス階層

BaseException
 ├── BaseExceptionGroup
 ├── GeneratorExit
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
      │    ├── FloatingPointError
      │    ├── OverflowError
      │    └── ZeroDivisionError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ExceptionGroup [BaseExceptionGroup]
      ├── ImportError
      │    └── ModuleNotFoundError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── MemoryError
      ├── NameError
      │    └── UnboundLocalError
      ├── OSError
      │    ├── BlockingIOError
      │    ├── ChildProcessError
      │    ├── ConnectionError
      │    │    ├── BrokenPipeError
      │    │    ├── ConnectionAbortedError
      │    │    ├── ConnectionRefusedError
      │    │    └── ConnectionResetError
      │    ├── FileExistsError
      │    ├── FileNotFoundError
      │    ├── InterruptedError
      │    ├── IsADirectoryError
      │    ├── NotADirectoryError
      │    ├── PermissionError
      │    ├── ProcessLookupError
      │    └── TimeoutError
      ├── ReferenceError
      ├── RuntimeError
      │    ├── NotImplementedError
      │    ├── PythonFinalizationError
      │    └── RecursionError
      ├── StopAsyncIteration
      ├── StopIteration
      ├── SyntaxError
      │    └── IndentationError
      │         └── TabError
      ├── SystemError
      ├── TypeError
      ├── ValueError
      │    └── UnicodeError
      │         ├── UnicodeDecodeError
      │         ├── UnicodeEncodeError
      │         └── UnicodeTranslateError
      └── Warning
           ├── BytesWarning
           ├── DeprecationWarning
           ├── EncodingWarning
           ├── FutureWarning
           ├── ImportWarning
           ├── PendingDeprecationWarning
           ├── ResourceWarning
           ├── RuntimeWarning
           ├── SyntaxWarning
           ├── UnicodeWarning
           └── UserWarning    

⇧ 基本的には、「Exception」クラスを親クラスとして例外クラスは作成されているはずですと。

Pythonのtry exceptが複数の時に例外の補足箇所には注意が必要な話

で、

docs.python.org

8.3. 例外を処理する

Exception はほぼすべての例外を捕捉するワイルドカードとして使えます。しかし良い例外処理の手法とは、処理対象の例外の型をできる限り詳細に書き、予期しない例外はそのまま伝わるようにすることです。

https://docs.python.org/ja/3.13/tutorial/errors.html#tut-userexceptions

⇧ 上記のようなことが記載されているのだけど、「Python」って「動的型付け言語」

なので、型の付与は実装者に委ねられていることもあり、戻り値の型とかハッキリしないので、当然のことながら、例外についても型がハッキリしませんと...

まぁ、破綻しているわけですと。

そして、今回の本題にも絡んでくるのだが、余程、明確に例外の型について説明してくれているライブラリを利用しているので無いならば、「Exception」で補足しておくのが無難と思われる。

で、本題。

■try exceptが複数

import logging

# ロガーの設定
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s"
)
logger = logging.getLogger(__name__)

try:
  err_message = ""

  # 何某かの処理1
  
except ValueError:
  err_message = "ValueError: "
  logger.exception(err_message)

except RuntimeError:
  err_message = "RuntimeError: "
  logger.exception(err_message)

try:
  # 何某かの処理2

except Exception:
  err_message = "Exception: "
  logger.exception(err_message)

try:
  # 何某かの処理3

except Exception:
  err_message = "Exception: "
  logger.exception(err_message)
  

⇧ のような処理があって、「何某の処理1」で

  • ValueError
  • RuntimeError

のどちらでも補足できない例外が起きた場合、「何某かの処理2」の方のexceptが「Exception」であるので、「何某かの処理2」の方のexceptで例外が補足されるのだが、「何某かの処理3」へは進んでくれずに、処理が終了してしまうという事象が起こったのである。

由々しき問題でありますと。

ネットの情報とかだと、

qiita.com

⇧ 処理が止まらないって話は出てくるんだが、処理が止まるっていう話が出てこないんよね...

今回は、

■try exceptが複数

import logging

# ロガーの設定
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s"
)
logger = logging.getLogger(__name__)

try:
  err_message = ""

  # 何某かの処理1
  
except ValueError:
  err_message = "ValueError: "
  logger.exception(err_message)

except RuntimeError:
  err_message = "RuntimeError: "
  logger.exception(err_message)

except Exception:
  err_message = "Exception: "
  logger.exception(err_message)

try:
  # 何某かの処理2

except Exception:
  err_message = "Exception: "
  logger.exception(err_message)

try:
  # 何某かの処理3

except Exception:
  err_message = "Exception: "
  logger.exception(err_message)
  

⇧ のように、exceptに「Exception」を追加して対応した。

とりあえず、

Exception はほぼすべての例外を捕捉するワイルドカードとして使えます。しかし良い例外処理の手法とは、処理対象の例外の型をできる限り詳細に書き、予期しない例外はそのまま伝わるようにすることです。

で不具合を発生させることになってしまうよりは、exceptに「Exception」を利用するで良いような気がする...

「理想」と「現実」は異なるということですかね...

ちなみに、

github.com

⇧「HVAC(HashiCorp Vault API client for Python 3.x)」というライブラリを利用していての話なのですが、

github.com

⇧ メソッドがどの例外を返す可能性があるのかサッパリ分からんので、exceptに「Exception」を利用せざるを得ないってことですかね...

毎度モヤモヤ感が半端ない…

今回はこのへんで。