14. 例外#

14.1. 例外の捕捉#

以下のコードは数値を\(0\)で割るため、実行するとエラー(例外)が発生する。

2. / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
/tmp/ipykernel_205130/4032807262.py in <module>
----> 1 2. / 0

ZeroDivisionError: float division by zero

プログラム中でエラーが発生したときは、例外クラスのオブジェクトが作成・送出される。例えば、上の例ではZeroDivisionErrorという例外クラスのオブジェクトが'float division by zero'というメッセージ付きで送出されている。

プログラムの実行中に例外が発生するとPythonインタプリタの実行が直ちに停止されるが、 try文およびexcept節を用いて、例外をプログラム中で捕捉することも可能である。

try:
    2. / 0
except ZeroDivisionError as e:
    print('ゼロ除算:', e)
ゼロ除算: float division by zero

try文を用いても、except節で指定した例外クラス以外の例外が発生した場合は、except節は実行されずPythonインタプリタの実行が止まってしまう。以下のプログラムで発生する例外はOverflowErrorであるため、ZeroDivisionErrorに関するexcept節では捕捉しきれない。

try:
    2. ** 1024
except ZeroDivisionError as e:
    print(e)
---------------------------------------------------------------------------
OverflowError                             Traceback (most recent call last)
/tmp/ipykernel_205130/1994322563.py in <module>
      1 try:
----> 2     2. ** 1024
      3 except ZeroDivisionError as e:
      4     print(e)

OverflowError: (34, 'Numerical result out of range')

except節を複数並べることで、異なる複数の例外に対する処理を記述できる。

try:
    2. ** 1024
except ZeroDivisionError as e:
    print('ゼロ除算:', e)
except OverflowError as e:
    print('オーバーフロー:', e)
オーバーフロー: (34, 'Numerical result out of range')
try:
    2. / 0
except ZeroDivisionError as e:
    print('ゼロ除算:', e)
except OverflowError as e:
    print('オーバーフロー:', e)
ゼロ除算: float division by zero

except節に例外クラスをタプルとしてまとめることで、異なる複数の例外に対する処理をまとめて記述できる。

try:
    2. ** 1024
except (ZeroDivisionError, OverflowError) as e:
    print('例外:', type(e), e)
例外: <class 'OverflowError'> (34, 'Numerical result out of range')
try:
    2. / 0
except (ZeroDivisionError, OverflowError) as e:
    print('例外:', type(e), e)
例外: <class 'ZeroDivisionError'> float division by zero

なお、ZeroDivisionErrorやOverflowErrorなどの組み込み例外の基底クラスはExceptionであるため、except節でExceptionクラスを指定すると、異なる種類の例外をまとめて捕捉できる。

try:
    2. ** 1024
except Exception as e:
    print('例外:', type(e), e)
例外: <class 'OverflowError'> (34, 'Numerical result out of range')
try:
    2. / 0
except Exception as e:
    print('例外:', type(e), e)
例外: <class 'ZeroDivisionError'> float division by zero

14.2. 例外の送出#

raise文を用いると、自分で実装したコードの中から例外を送出できる。

def timestamp(hour, minute, second):
    if hour < 0 or 24 <= hour:
        raise ValueError("引数hourは0 <= hour < 24を満たす必要があります")
    if minute < 0 or 60 <= minute:
        raise ValueError("引数minuteは0 <= minute < 60を満たす必要があります")
    if second < 0 or 60 <= second:
        raise ValueError("引数secondは0 <= second < 60を満たす必要があります")
    return hour * 3600 + minute * 60 + second
timestamp(2, 43, 70)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/tmp/ipykernel_205130/3015373880.py in <module>
----> 1 timestamp(2, 43, 70)

/tmp/ipykernel_205130/1757576539.py in timestamp(hour, minute, second)
      5         raise ValueError("引数minuteは0 <= minute < 60を満たす必要があります")
      6     if second < 0 or 60 <= second:
----> 7         raise ValueError("引数secondは0 <= second < 60を満たす必要があります")
      8     return hour * 3600 + minute * 60 + second

ValueError: 引数secondは0 <= second < 60を満たす必要があります

関数の呼び出し側にtry文を埋め込み、例外を捕捉することも可能である。

try:
    timestamp(48, 0, 0)
except Exception as e:
    print('例外:', type(e), e)
例外: <class 'ValueError'> 引数hourは0 <= hour < 24を満たす必要があります

例外クラスを自分で定義することもできる。ユーザ定義の例外クラスはExceptionを継承する必要がある。

class TimestampError(Exception):
    pass

def timestamp(hour, minute, second):
    if hour < 0 or 24 <= hour:
        raise TimestampError("引数hourは0 <= hour < 24を満たす必要があります")
    if minute < 0 or 60 <= minute:
        raise TimestampError("引数minuteは0 <= minute < 60を満たす必要があります")
    if second < 0 or 60 <= second:
        raise TimestampError("引数secondは0 <= second < 60を満たす必要があります")
    return hour * 3600 + minute * 60 + second
timestamp(1, 95, 22)
---------------------------------------------------------------------------
TimestampError                            Traceback (most recent call last)
/tmp/ipykernel_205130/3588777746.py in <module>
----> 1 timestamp(1, 95, 22)

/tmp/ipykernel_205130/1955752630.py in timestamp(hour, minute, second)
      6         raise TimestampError("引数hourは0 <= hour < 24を満たす必要があります")
      7     if minute < 0 or 60 <= minute:
----> 8         raise TimestampError("引数minuteは0 <= minute < 60を満たす必要があります")
      9     if second < 0 or 60 <= second:
     10         raise TimestampError("引数secondは0 <= second < 60を満たす必要があります")

TimestampError: 引数minuteは0 <= minute < 60を満たす必要があります

今回の引数チェックの場合、hour, minute, secondのそれぞれに対し、別の例外クラスを定義するのも一案である。

class TimestampError(Exception):
    pass

class TimestampHourError(TimestampError):
    pass

class TimestampMinuteError(TimestampError):
    pass

class TimestampSecondError(TimestampError):
    pass

def timestamp(hour, minute, second):
    if hour < 0 or 24 <= hour:
        raise TimestampHourError("引数hourは0 <= hour < 24を満たす必要があります")
    if minute < 0 or 60 <= minute:
        raise TimestampMinuteError("引数minuteは0 <= minute < 60を満たす必要があります")
    if second < 0 or 60 <= second:
        raise TimestampSecondError("引数secondは0 <= second < 60を満たす必要があります")
    return hour * 3600 + minute * 60 + second
try:
    timestamp(2, 68, 0)
except Exception as e:
    print('例外:', type(e), e)
例外: <class '__main__.TimestampMinuteError'> 引数minuteは0 <= minute < 60を満たす必要があります

なお、デバッグ目的であればassert文を使い、引数の値をチェック(テスト)して、違反していたらAssertionErrorを送出することもできる。

def timestamp(hour, minute, second):
    assert 0 <= hour < 24, "引数hourは0 <= hour < 24を満たす必要があります"
    assert 0 <= minute < 60, "引数minuteは0 <= minute < 60を満たす必要があります"
    assert 0 <= second < 60, "引数secondは0 <= second < 60を満たす必要があります"
    return hour * 3600 + minute * 60 + second
timestamp(2, 43, 70)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
/tmp/ipykernel_205130/3015373880.py in <module>
----> 1 timestamp(2, 43, 70)

/tmp/ipykernel_205130/811021604.py in timestamp(hour, minute, second)
      2     assert 0 <= hour < 24, "引数hourは0 <= hour < 24を満たす必要があります"
      3     assert 0 <= minute < 60, "引数minuteは0 <= minute < 60を満たす必要があります"
----> 4     assert 0 <= second < 60, "引数secondは0 <= second < 60を満たす必要があります"
      5     return hour * 3600 + minute * 60 + second

AssertionError: 引数secondは0 <= second < 60を満たす必要があります

14.3. よく見かける例外#

文法エラー

2 / / 1
  File "/tmp/ipykernel_205130/3671002116.py", line 1
    2 / / 1
        ^
SyntaxError: invalid syntax

インデントの間違い

for i in range(10):
    if i % 2 == 0:
    print('even')
  File "/tmp/ipykernel_205130/2214768420.py", line 3
    print('even')
    ^
IndentationError: expected an indented block after 'if' statement on line 2

オーバーフロー

2. ** 1024
---------------------------------------------------------------------------
OverflowError                             Traceback (most recent call last)
/tmp/ipykernel_205130/997473611.py in <module>
----> 1 2. ** 1024

OverflowError: (34, 'Numerical result out of range')

未定義の変数

a * 2
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/tmp/ipykernel_205130/188921875.py in <module>
----> 1 a * 2

NameError: name 'a' is not defined

範囲外のインデックス

x = [1, 2, 3]
x[4]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
/tmp/ipykernel_205130/3042274859.py in <module>
      1 x = [1, 2, 3]
----> 2 x[4]

IndexError: list index out of range

キーが見つからない

y = {'one': 1, 'two': 2, 'three': 3}
y['four']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/tmp/ipykernel_205130/1027427460.py in <module>
      1 y = {'one': 1, 'two': 2, 'three': 3}
----> 2 y['four']

KeyError: 'four'

型の不整合

x + y
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_205130/978381659.py in <module>
----> 1 x + y

TypeError: can only concatenate list (not "dict") to list

ファイルが見つからない

open('hoge.txt')
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
/tmp/ipykernel_205130/1539795522.py in <module>
----> 1 open('hoge.txt')

FileNotFoundError: [Errno 2] No such file or directory: 'hoge.txt'