概要
プログラムで動作している際に、予期せぬエラーによってプログラムが落ちる場合がある。
この予期せぬエラーのことを例外(Exception)という。
例外が発生した場合もプログラムを落とさないよう対策(例外処理)してプログラミングをする必要がある。
今回はこの例外処理について紹介する。
例外が発生するケース
例えば、例外が発生するケースは以下のようなケースがある。
変数名が誤っている
# 変数名を誤っているケース
a = 100
print(b) # NameError: name 'b' is not defined
# 正:print(a)
aという変数を定義しているのに、定義していないbを使用している。
関数名が誤っている
# 関数名を誤っているケース
def add(a, b):
return a + b
print(sdd(10, 20)) # NameError: name 'sdd' is not defined
# 正:print(add(10, 20))
addという関数を定義しているのに、定義していないsddを使用している。
文字列と数値を加算しようとしている
c = 10
d = "20"
c += d
print(c) # TypeError: unsupported operand type(s) for +=: 'int' and 'str'
# 正:c += int(d)
数値型である変数cと文字列である変数bを計算しようとしている。
要素外のインデックスを指定した
num_list = [1, 2, 3, 4, 5]
print(num_list[10]) # IndexError: list index out of range
# 正:print(num_list[4])
要素数が4までしか存在しない配列に対して、要素外の10を指定している。
存在しないキーを指定した
num_dict = {"a": 1, "b": 2}
print(num_dict["c"]) # KeyError: 'c'
# 正:num_dict['b']
dict型の変数に対して、存在しないキーを指定している。
エラーになる引数を指定された(ゼロ除算)
# 割り算結果を出力する(余りは除外)
def culc_devide(total, num):
res = total // num
print(res)
culc_devide(100, 20) # 5
culc_devide(30, 0) # ZeroDivisionError: integer division or modulo by zero
存在しない日付を変換した
# 存在しない日付
some_date = "2023/7/77"
str_date = datetime.strptime(some_date, "%Y/%m/%d")
print(str_date) # ValueError: unconverted data remains: 7
# 正:some_date = "2023/7/7"
例外への対処
プログラミングをする場合、予期せぬエラーによってシステムを落とさない考慮が必要になる。
そのため、想定される例外に対しては予め対策を行う。
try-except句
try-except句を使用することで、例外が発生した場合の処理を記述できる。
例外が起こりうる処理
except 例外クラス:
例外が発生した場合の処理
以下はゼロ除算により、ZeroDivisionErrorが発生してシステムが落ちるケース。
予めZeroDivisionErrorが起こりうることを想定して、エラーメッセージ対処を行う。
# 割り算結果を出力する(余りは除外)
def culc_devide(total, num):
try:
# ①正常ケース
res = total // num
print(res)
except ZeroDivisionError:
# ②ゼロ除算ケース
print("0で割らないでください")
# メイン実行関数
def main():
culc_devide(100, 20) # 5
culc_devide(30, 0) # 0で割らないでください
culc_devide(50, 30) # 1
if __name__ == "__main__":
main()
try-except-finally句
finallyを使用することで、例外の発生有無にかかわらず最後に必ず行う共通処理を記述できる。
例外が起こりうる処理
except 例外クラス:
例外が発生した場合の処理
finally:
共通処理
以下はfinallyを使用して、処理の終わりに共通メッセージを出力している。
# 割り算結果を出力する(余りは除外)
def culc_devide(total, num):
try:
# ①正常ケース
res = total // num
print(res)
except ZeroDivisionError:
# ②異常(ゼロ除算)ケース
print("0で割らないでください")
finally:
print("処理を終了します") # 正常でも異常でも必ず実行
# メイン実行関数
def main():
culc_devide(100, 20) # 5
culc_devide(30, 0) # 0で割らないでください
culc_devide(50, 30) # 1
if __name__ == "__main__":
main()
#-------------------------------------------------
# 出力
#5
#処理を終了します
#0で割らないでください
#処理を終了します
#1
#処理を終了します
正常の場合でも異常の場合でも必ず「処理を終了します」が出力される。
raise
意図的に例外を発生させることによって、関数の呼び出しもとでエラー制御を行う場合がある。
そんなときにraiseを使用する。
プログラミング言語によっては例外をスロー(発生)させるとも言う。
def culc_devide(total, num):
# 計算対象が両方0の場合、ValueErrorを発生させる
if total == 0 and num == 0:
raise ValueError("計算対象のどちらも0です")
try:
# ①正常ケース
res = total // num
print(res)
except ZeroDivisionError:
# ②異常(ゼロ除算)ケース
print("0で割らないでください")
finally:
print("処理を終了します") # 正常でも異常でも必ず実行
# メイン実行関数
def main():
culc_devide(100, 20) # 5
culc_devide(30, 0) # 0で割らないでください
culc_devide(50, 30) # 1
culc_devide(0, 0) # ValueError: 計算対象のどちらも0です
if __name__ == "__main__":
main()
上記は意図的にValueErrorを発生させているが、例外処理ができていない。
意図的に例外を発生させる場合、以下のようにraiseを使用している関数の呼び出し元で例外処理を行う。
def culc_minus(total, num):
if total < num:
raise ValueError("計算結果がマイナスになってしまいます")
print(total - num)
# メイン実行関数
def main():
try:
culc_minus(10, 5)
culc_minus(5, 10)
except ValueError as e:
print(e.args[0])
if __name__ == "__main__":
main()
#-------------------------------------------------
# 出力
#5
#計算結果がマイナスになってしまいます
また、以下のようにexceptに例外クラスを使用しない記述方法でも例外処理はできる。
def culc_minus(total, num):
if total < num:
raise ValueError("計算結果がマイナスになってしまいます")
print(total - num)
# メイン実行関数
def main():
try:
culc_minus(10, 5)
culc_minus(5, 10)
except:
print("例外が発生した")
if __name__ == "__main__":
main()
#-------------------------------------------------
# 出力
#5
#例外が発生した
しかし、本来はプログラムのバグでシステムが落ちるべきケースもexceptでエラーを握りつぶしてしまうため、
基本的に例外クラスを指定するべき。