前回の記事でPythonにおけるクラスの定義やインスタンスの作成方法、オブジェクト指向の考え方などについて学びました。
今回は、プログラム実行時に起こる例外処理について学んでいきたいと思います。
例外処理ってプログラムで予想外のことが起こったときの対処って感じかな
はい、実行時に予期せぬ問題が発生した場合に、プログラムとして正しく処理を行う必要があります。
そして、その例外を正常に処理する仕組みがPythonに組み込まれています。
- 例外処理とは何かがわかる
- Pythonでの例外処理の方法や使い方がわかる
例外を処理する
そもそも例外とは何でしょうか?
例外とは「プログラムの実行中に何らかの原因でプログラムの実行を正常に続けられなくなったときに発生するもの」です。
これが発生するとプログラムがその場で中断されます。
例外は実行中のプログラムの状況に応じて発生し、例えばその時の変数の値や、ユーザからの入力値などによって発生します。
例えば以下のようなプログラムがあります。
n = input("整数を入力して下さい:")
n = int(n)
result = n * n
print(str(n)+"の2乗は"+str(result))
これは数字を入力したら、その値を2乗して表示するプログラムです。
例えば「10」を入力すると、10を2乗した値なので「100」と返ってきます。
整数を入力して下さい:10
10の2乗は100
ただし、ユーザが整数以外の値を入力したらどうなるでしょうか。
例えば「あああ」と入力すると、以下のようにエラーとなります。
整数を入力して下さい:あああ
Traceback (most recent call last):
File "/Users/xxxxxxx/Documents/python_code/test2.py", line 2, in <module>
n = int(n)
ValueError: invalid literal for int() with base 10: 'あああ'
上記のように整数で入力するところを文字列で入力したため「ValueError」が発生してしまい、そのまま処理が止まってしまいます。
いやいや、整数入力して下さいって書いてあるのに文字列入れる方が悪いでしょう
でもプログラムは何が起こるか分かりません。
ユーザが誤って文字列を入れた場合に、その度にプログラムが止まるようではあまりにもプログラムとして不親切です。
tryによる例外処理
こうした例外が発生した場合に、Pythonでは例外処理を行うための専用の構文が用意されています。
tryの次行からインデントして、例外が発生する(可能性のある)処理を記述します。
ここのtryの範囲で例外が発生すると「except」のところにジャンプします。
exceptには例外クラス名が指定されており、一致する例外クラスが発生した場合にexceptでキャッチしてその例外処理が実行されます。
ただしその例外クラスをキャッチできるexceptが定義されていなければ、例外は受け止められないでそのままプログラムは中断します。
finallyは構文を抜ける際に必ず実行する処理を書きます。
この処理は例外が発生してもしなくても必ず実行されます。
またfinallyはオプションですので、不要であれば省略は可能です。
また、tryを定義したら、必ず「except」か「finally」は定義しなくてはなりません。
では先ほどのプログラムを例外処理を使って書き直してみましょう。
n = input("整数を入力して下さい:")
try:
n = int(n)
result = n * n
print(str(n)+"の2乗は"+str(result))
except ValueError:
print("["+n+"」は整数ではありません、値を正しく入力して下さい")
上記4行目で、try内で入力値をint型に変換しています。
ここで変換エラーとなると「ValueError」例外クラスが発生し、5行目以降の処理をスキップして8行目のValueErrorで例外をキャッチしてエラー文言を表示します。
これでエラーが発生してもユーザにエラー内容を促しており、プログラムも中断することなく正常に処理が終了されます。
複数のexcept
try構文ではexceptで例外をキャッチしますが、exceptは複数の例外をキャッチすることが出来ます。
その場合は、例外処理の構文にexceptを複数定義します。
実際にプログラムを書いてみましょう。
data = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]
n = input("0-6の数字を入力して下さい:")
try:
n = int(n)
result = data[n]
print(result)
except ValueError:
print("["+n+"」は数字ではありません、値を正しく入力して下さい")
except IndexError:
print("0-6までの数字を入力して下さい")
0−6までの数字を入力し、その数字に合った曜日を返すプログラムです。
ここで文字列を入力するとValueError例外クラスを10行目でキャッチして「数字ではありません、値を正しく入力して下さい」と表示されます。
また0−6以外の数字を入力するとIndexError例外クラスを13行目でキャッチして「0−6までの数字を入力して下さい」と表示されます。
実行結果は以下となります。
0-6の整数を入力して下さい:111
0-6までの数字を入力して下さい
0-6の整数を入力して下さい:aa
[aa」は数字ではありません、値を正しく入力して下さい
0-6の数字を入力して下さい:6
Sat
except Exception:について
例外処理でどのエラーが発生するか分からないが、全てのエラーをキャッチしたい場合は「except Exception:」と定義すれば、全ての例外処理をキャッチすることが出来ます。
例えば先ほどのプログラムのexceptを「except Exception」に置き換えると以下となります。
data = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]
n = input("0-6の数字を入力して下さい:")
try:
n = int(n)
result = data[n]
print(result)
except Exception:
print("実行中にエラーが発生しました")
try構文の中でどんなエラーが発生しても「Exception」で受け取るので、エラーが発生すると「実行中にエラーが発生しました」というメッセージが出力されます。
ただし、どんな例外もキャッチしてしまうので上記の処理では実際にどの例外が発生したかは分かりません。
ですので使い方としては、exceptを複数定義した最後に「except Exception」を定義して、全てのエラーをキャッチするというのがよく使われる方法です。
例外クラスのインスタンス
例外クラスをキャッチした際に、その例外のインスタンスを変数に格納することが出来ます。
例えば、先ほどの「except Exception」でキャッチする処理だと、何の例外が発生したか分かりませんでしたが、例外を変数に代入することで例外のインスタンスから情報を取得することが出来ます。
先ほどのプログラムを少し修正してみましょう。
data = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]
n = input("0-6の数字を入力して下さい:")
try:
n = int(n)
result = data[n]
print(result)
except Exception as err:
print("例外:"+str(err.args))
プログラムの10行目で「err」変数で例外をインスタンスに保持して、そのインスタンスから内容を出力しています。
こうすることで、エラーが起こった際にキャッチした例外のインスタンスによってメッセージが変わります。
0-6の数字を入力して下さい:99
例外:('list index out of range',)
0-6の数字を入力して下さい:aaa
例外:("invalid literal for int() with base 10: 'aaa'",)
例外を発生させる
これまでは、発生した例外をキャッチしてプログラムを中断させずに正しいエラー処理に導く方法を学びました。
今度は逆に「必要に応じて例外を作り出す」方法を学びます。
というのも、クラスや関数(メソッド)を作成した際に、処理中に問題が発生した場合に呼び出し側にきちんと知らせる必要があるからです。
このように発生したエラーを呼び出し側に正しく知らせるために、例外を作成して送信する方法を理解しておきましょう。
raiseによる例外
まずは、exceptで受け止めた例外をそのまま再度送り出す方法を説明します。
例外をそのまま再送出する場合は、引数もなく「raise」と記述するだけで再送出されます。
raiseするとその時点で例外が再送出されて、プログラムは中断されます。
ですのでraise以降に処理が書いてあっても実行されないで終わりますので注意が必要です。
先ほどの例外クラスのインスタンスを変数に格納するプログラムを一部修正してみます。
data = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]
n = input("0-6の数字を入力して下さい:")
try:
n = int(n)
result = data[n]
print(result)
except Exception as err:
if isinstance(err,ValueError):
print("ValueError:"+str(err.args))
else:
raise
Exceptionで受けるところまでは一緒ですが、例外インスタンスが「ValueError」以外であれば、14行目の「raise」でそのまま例外を再送出しています。
ここでIndexErrorを起こすと、例外が送出されているのが分かります。
0-6の数字を入力して下さい:99
Traceback (most recent call last):
File "/Users/xxxxxxx/Documents/python_code/test2.py", line 7, in <module>
result = data[n]
IndexError: list index out of range
例外インスタンスをraiseする
処理中で新たに例外を送出することも出来ます。
このような場合は、新たに例外インスタンスを作成してraiseします。
このようにraiseの後に例外インスタンスを指定すれば、その例外を送出します。
n = input("整数を入力して下さい:")
try:
n = int(n)
if n == 0:
raise ZeroDivisionError("0で除算したらよろしゅうない")
result = 100/n
print("100÷"+str(n)+"="+str(result))
except Exception as err:
print("例外:"+str(err.args))
入力した数字で100を割るというプログラムです。
入力した数字が「0」の場合は0での除算となりますので、7行目で「ZeroDivisionError」の例外インスタンスを作成して送出しています。
上記例外インスタンスは12行目でキャッチされますので、そこでエラー処理されます。
0で割った際の実行結果は以下となります。
整数を入力して下さい:0
例外:('0で除算したらよろしゅうない',)
独自の例外クラスを作る
では最後に独自の例外クラスを作成して、そのインスタンスを送出してみましょう。
独自の例外クラスを作成する場合は、Exceptionクラスを継承して作成します。
例外クラス内でメソッドは自由に定義できますが、コンストラクタ(__init__)は必ず定義する必要があります。
エラーメッセージなどの必要な値はコンストラクタで引数を用意して渡します。
では実際に作ってみましょう。
先ほどの0で除算したプログラムで「ZeroDivisionError」を使わずに新規の例外クラスである「MyError」クラスを作成してみましょう。
class MyError(Exception):
def __init__(self, *args):
self.args = args
def __str__(self):
return str(self.args[0]) + "(問題の値:" + str(self.args[1]) +")"
n = input("整数を入力して下さい:")
try:
n = int(n)
if n == 0:
raise MyError("0で除算したらよろしゅうない",0)
result = 100/n
print("100÷"+str(n)+"="+str(result))
except MyError as err:
print(err)
1行目でExceptionクラスを継承したMyErrorクラスを作成しています。
そして入力値が0の場合に、MyErrorクラスのインスタンスを作成して送出しています。
MyErrorインスタンスは19行目でキャッチされて出力されます。
実行結果は以下となります。
整数を入力して下さい:0
0で除算したらよろしゅうない(問題の値:0)
まとめ
今回は、Pythonにおける例外処理についての方法や実際の使い方などを説明しました。