例外処理
今日はエラーについて解説していきます。初歩的な内容ですが、エラーなんてtry~except
書いときゃいいんでしょ?という方は、参考にしてみてください。また、説明に誤りがある場合コメントをいただけると幸いです。
さて、Pythonで生じるエラーは主に二つに大別されます。構文エラー(syntax error)と例外(exception)です。
構文エラー
これは、プログラムを実行する前から誤っていると判断できる際に起こります。例えば、識別子(identifier)の規則を満たしていない変数名を使ったり、インデントがおかしかったり、とにかく文法がおかしい時に生じるエラーのことです。a[0]
をa[0}
と書いたり、if
をof
と書いたり...(これやったことありますか?僕はあります...)例を上げきれませんね。簡単に言えば、構文エラーはプログラムを書く練習をしていれば減ってくる文法ミスです。一応例を書いておきます笑
#構文エラーの例
a = [1, 2, 3]
print(a[3})
出力
print(a[3})
^
SyntaxError: invalid syntax
例外
これはプログラムを実行中にコンピュータが(インタプリタが?)「こいつは処理できないぞ?」と判断した際に生じるエラーです。簡単な例で言えば
a = [1, 2, 3]
print(a[3])
出力
1 a = [1, 2, 3]
----> 2 print(a[3])
IndexError: list index out of range
配列の要素はインデックス番号2までしか存在しませんから、a[3]
を出力しようとすると当然例外が発生します。ここでは例外の例としてIndexError
を示しましたが、まだまだたくさんあります。
例外を捕捉する
さて、プログラムを実行した時に、例外が発生しても処理を続けたい場合があります。その際はお馴染みのtry~except
を使います。例外処理によってZeroDivisionError
が出てもプログラムが止まらないようにしてみましょう。
a = 1
b = 0
try:
print(a/b)
except ZeroDivisionError:
print('0で割っちゃダメでしよ?ZeroDivisionErrorがでたでし')
finally:
print('割り算はむずかちぃでし')
出力
0で割っちゃダメでしよ?ZeroDivisionErrorがでたでし
割り算はむずかちぃでし
上の例のように、最後の後始末的にfinally
をつけてもいいです。これは例外が発生しようがしまいが最後に実行され、そこで処理が中断、終了します。
複数の例外を捕捉する
except
には、次のようにタプルで複数の例外を捕捉できるよう指定できます。
try:
a = int(input('a = '))
b = int(input('b = '))
print(a/b)
except (ValueError, ZeroDivisionError):
print('ValueErrorかZeroDivisionErrorが起こっているでし')
出力1
a = 1.5
ValueErrorかZeroDivisionErrorが起こっているでし
出力2
a = 1
b = 0
ValueErrorかZeroDivisionErrorが起こっているでし
例外に名前を付ける
さらに、発生した例外に名前をつけることもできます。以下では、ValueError
かZeroDivisionError
が発生するよう入力していますが、発生した例外を捕獲し、ER
という名前をつけています。
try:
a = int(input('a = '))
b = int(input('b = '))
print(a/b)
except (ValueError, ZeroDivisionError) as ER:
print(f'{type(ER)}が起こっているでし')
出力1
a = 1.5
<class 'ValueError'>が起こっているでし
出力2
a = 1
b = 0
<class 'ZeroDivisionError'>が起こっているでし
このER
という名前はexcept
のスイート内でしか使えないので気をつけてください。次のようにするとNameError
が発生してしまいます(せっかくなのでこのNameError
も捕捉してやりましょう)。
try:
a = int(input('a = '))
b = int(input('b = '))
print(a/b)
except (ValueError, ZeroDivisionError) as ER:
pass
try:
print(ER)
except NameError:
print('ERって何のことでしか?')
出力
a = 1.5
ERって何のことでしか?
raiseで例外を発生させる
raise
によって意図的に例外を発生させることもできます。ただしここで使えるのはBaseExceptionクラス
のサブクラスかインスタンスに限られます。(後述しますが、よく見る例外はほとんどこのBaseException
のサブクラスです。)
def ErFunc(b: int) -> None:
if b == 0:
raise ZeroDivisionError
b = 0
try:
ErFunc(b)
except BaseException as ER:
print(f'{type(ER)}が発生してるでし')
出力
<class 'ZeroDivisionError'>が発生してるでし
ここではBaseExceptionクラス
が指定されているので、その派生クラスは何でも捕捉できるようになっています。ここではZeroDivisionError
が捕捉されています。
ユーザー定義の例外
最後に、クラスを使ってユーザー定義の例外クラスを作成することもできます。以下は一桁の二つの自然数の和を求めるプログラムですが、負の数や二桁の数が入力されたり、和が二桁になると例外を発生させるようにします。ParameterRangeException
クラスもReturnRangeException
クラスもどちらも範囲に関するクラスなので、RangeException
クラスから継承しています。もちろん、標準組み込みクラスであるException
クラスから継承するようにしても問題ありません。
class RangeException(Exception):
pass
class ParameterRangeException(RangeException):
pass
class ReturnRangeException(RangeException):
pass
def add(a: int, b: int) -> int:
if not 0 < a < 10:
raise ParameterRangeException
if not 0 < b < 10:
raise ParameterRangeException
if a + b > 10:
raise ReturnRangeException
else:
return a + b
try:
a = int(input())
b = int(input())
print(f'二数の和は{add(a, b)}でし')
except ParameterRangeException as PRE:
print(f'{type(PRE)}が生じてるでし。仮引数は1~9じゃなきゃダメでし')
except ReturnRangeException as RRE:
print(f'{type(RRE)}が生じてるでし。返却値を9以下にしなきゃダメでし')
finally:
print('例外の説明は以上でし')
出力1
1
8
二数の和は9でし
例外の説明は以上でし
出力2
2
11
<class '__main__.ParameterRangeException'>が生じてるでし。仮引数は1~9じゃなきゃダメでし
例外の説明は以上でし
出力3
5
7
<class '__main__.ReturnRangeException'>が生じてるでし。返却値を9以下にしなきゃダメでし
例外の説明は以上でし
補足
Pythonに用意されている標準組み込み例外の親クラスは
BaseException
クラスです。これを親として、Exception
クラスがあり、これを継承した形でValueError
クラスやIndexError
クラスが存在しています。ユーザー定義の例外を作る場合、
UserExceptionClass()
の引数にBaseException
を入れてはいけません。仕様的にBaseException
クラスはユーザー定義例外に継承されることを前提としていないようです。BaseException
クラスの子クラスとしてArithmeticError
クラスがありますが、このクラスの下にOverflowError
クラスやZeroDivisionError
クラスが存在します。もし算術エラーを扱いたいのであれば、ユーザー定義例外を作る際、引数にArithmeticError
を入れても構いません(Exception
クラスまたはそれよりも下部のクラスであれば何でもOK)。