LoginSignup
2
4

More than 1 year has passed since last update.

例外となかよく

Last updated at Posted at 2023-04-24

はじめに

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: ゼロ除算が発生したで

ただしBaseExceptionGroupExceptionGroupなどは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表示

エラーと例外

組み込み例外

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4