はじめに
Pythonコードを書いていて「ここでエラーを発生させてぇ」って思うことありますよね。私はあります。
今回はエラー対処ではなく、エラーそのものについて書いてみます。
2種類の「エラー」
「エラー」と口語的書いているが、Pythonの「エラー」は大雑把に2種類に分類できる。
ひとつは構文エラー (syntax error) 、もうひとつは 例外 (exception)
2種類の「エラー」
-
構文エラー (syntax error)
いわゆる文法エラー、Pythonの文法そのものが間違っている場合に発生
構文解析エラー (parsing error) とも言うらしい -
例外 (exception)
文法自体は正しいが、stack tracebackを残してプログラムが終了する
今回の記事は「例外 (exception)」について記載している。
組み込み例外
Python純正の組み込み例外は、次のようなヒエラルキー構造になっている
組み込み例外一覧
組み込み例外のクラス階層は以下のとおりです:
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 │ └── 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
Python公式( https://docs.python.org/ja/3/library/exceptions.html#exception-hierarchy )から引用
例外を回避したい(try-except
文)
本来であればstack tracebackを見てなぜ例外が発生したのか?どうすれば例外を回避できるか?を本質的に考えるべきである。
が、戦略的撤退をすることも時として必要である。その場合try-except
文を使用する。
try-except
文
try:
# 例外が起こりそうな処理
except:
# 例外が起きたときの処理
まずはtry
文を実行する。
何かしらの例外が発生したらtry
文の処理を中断、except
文の処理を開始する。この時tracebackは出力されない。
try
文で例外が発生しなければexcept
文の処理は行われない。
必ずしもすべてのエラーから逃げる必要がない場合は、下記のようにexcept
文に避けたい例外だけを列挙すればOK
特定の例外だけを明記してよけたい場合
try:
# 例外が起こりそうな処理
except (RuntimeError, TypeError, NameError) as e:
# 例外が起きたときの処理
as e
は書かなくても良いが、書いておくと色々遊べる(後述)
上位階層の例外名を書くと、下位の例外もまとめて処理することができる。
例えばArithmeticError
は下位にFloatingPointError,OverflowError,ZeroDivisionError
を持つのでこれらもまとめてexcept
文で捕まえることができる。
ArithmeticError
├── FloatingPointError
├── OverflowError
└── ZeroDivisionError
例外を起こしたい(raise
文)
例外を起こすにはraise
文を使う。これで好きな例外を好きなときに好きなだけ発生させることができる。
例外の名前と、そのときに表示するメッセージを書けば良い。
「例外名」は上記の例外クラス階層から選ぶ。(名前を間違えるとNameError
になってしまう)
raise
文
raise 例外名("メッセージ")
例えば0で割り算をしたときのエラーZeroDivisionError
を発生させたいときは以下のように書けば良い。
raise ZeroDivisionError("ゼロ除算が発生したで")
# 出力
# Traceback (most recent call last):
# 有象無象のメッセージ
# raise ZeroDivisionError("ゼロ除算が発生したで")
#
# ZeroDivisionError: ゼロ除算が発生したで
ただしBaseExceptionGroup
やExceptionGroup
などはraise
できない
自作の例外を作りたい!
作り方は簡単で、たった一行でオリジナルの例外を作成できる。
Exception
クラスを継承して作成する。例外名を自分で設定できる。
class 例外名(Exception): ...
Exception
部分は別の例外クラスを指定することも可能だが、公式には「Exception
クラスから派生させるべきである」と記載されています。
exception Exception
システム終了以外の全ての組み込み例外はこのクラスから派生しています。全てのユーザ定義例外もこのクラスから派生させるべきです。Python公式( https://docs.python.org/ja/3/library/exceptions.html#Exception )から引用
あとはif
文などで適当に導火線を設定し、オリジナルの例外をraise
で起爆すれば良い。
# 例外の定義
class OriginalError(Exception): ...
# 例外を発生
raise OriginalError("オリジナルのエラー")
# 出力
# Traceback (most recent call last):
# 有象無象のメッセージ
# raise OriginalError("オリジナルのエラー")
#
# OriginalError: オリジナルのエラー
その他tips
stack tracebackで遊びたい人向け
except
文のas e
の意味
前述の通り、as e
はなくても良い。しかし受け取っておくことでこんな遊びもできる。
try:
raise Exception("エラーやで")
except Exception as e:
print(e)
print(e.__class__)
print(e.__class__.__name__)
# 出力
# エラーやで
# <class 'Exception'>
# Exception
tracebackメッセージを短くする
sys
モジュールをimportし、tracebacklimit
を設定することでstack tracebackの打ち切りラインを設定できる。
デフォルトはsys.tracebacklimit=1000
import sys
sys.tracebacklimit=整数
sys.tracebacklimit=0
とすると「どこで発生したか」の情報を一切言わなくなる。
import sys
sys.tracebacklimit=0
raise Exception("エラーやで")
# 出力
# Exception: エラーやで
tracebackメッセージをstrで受け取る
こういうことをしたいときに便利
traceback
モジュールをimportし、traceback.format_exc()
を使用するとtracebackメッセージをstrで受け取れる。
引数のlimit
に整数を指定することでtracebacklimit
と同じように、どこまで遡るかを指定できる。
変数errstrにtracebackメッセージを文字列として受け取ることができる
import traceback
try:
raise Exception("エラーやで")
except Exception as e:
errstr = traceback.format_exc(limit=None)
参考
tracebackをGUI表示
エラーと例外
組み込み例外