Python

Python基礎講座(11 例外)

More than 3 years have passed since last update.

例外とは?

コードに間違いがあり、プログラムをコンパイルした際にエラーが発生することを「コンパイルエラー」と言いますが、
コンパイルは正常に終了しても、その後実行中に何らかの異常が発生することを例外と言います。
自分の記事の中ではこれまで意味が伝わるように「エラー」という言葉で「例外」を表現していましたが、
今後は正しい単語である「例外」を使用していきます。

これまでの記事で紹介した例外は以下のようなものがありました。

  • 数値型と文字列型を+で結合する
  • リスト.remove(要素)でリスト内に存在しない要素を削除しようとする
  • ディクショナリでget(キー)を用いずに、存在しないキーを指定する

他にも有名な例外を発生させるコードが、数値を0で割るゼロ除算です。

division_by_zero.py
a = 10 / 0
print("{0}".format(a))

上記プログラムはコンパイル時にはエラーを発生させませんが、実行時に以下のように例外を発生させます。

ZeroDivisionError: integer division or modulo by zero

例外を発生しうるコードの箇所に「もし例外が発生したら~」と記述することを例外処理と言います。

例外の捕捉

プログラムが実行中に突然終了してしまっては困ります。
そこで、上に書いたゼロ除算のプログラムを、実行時に例外を発生させないよう修正します。

division_by_zero2.py
try:
    a = 10 / 0
    print("{0}".format(a))
except ZeroDivisionError:
    print("ZeroDivisionError!!")

例外を発生しうるコードをtry:で括ります。そして例外が発生した場合の処理をexcept 例外の種類 :で括ります。
「例外の種類」は適切なものを記述しなければなりません。
例えば上記のプログラムでZeroDivisionError(=ゼロ除算例外)ではなく、
以下のようにValueError(=変数の型に合わない値が格納された例外)を指定すると、例外を捕捉しません。

division_by_zero_EXCEPTION2.py
try:
    a = 10 / 0
    print("{0}".format(a))
except ValueError:
    print("ZeroDivisionError!!")

上記のプログラムを実行すると例外が発生します。

例外に関する情報の利用

先ほどのプログラムでは例外が発生した場合、自分で出力するメッセージを作成していましたが、
Pythonでは例外発生時に情報を保持しているため、これを利用できます。
以下のプログラムを実行してみてください。

py3.division_by_zero_info.py
try:
    a = 10 / 0
    print("{0}".format(a))
except ZeroDivisionError as e:
    print("type:{0}".format(type(e)))
    print("args:{0}".format(e.args))
    print("message:{0}".format(e.message))
    print("{0}".format(e))

exception 例外の種類 as 変数 : と記述すると、エラーの情報を持つ変数を定義できます。
出力結果は以下のようになります。自分で作成したメッセージの代わりにこれらを出力しても良いでしょう。

type:<type 'exceptions.ZeroDivisionError'>
args:('integer division or modulo by zero',)
message:integer division or modulo by zero
integer division or modulo by zero

複数の例外を捕捉する

先ほど書いたように、「例外の種類」は適切なものを記述しなければなりませんが、複数パターンの例外が
発生する場合は、分岐のelif同様に複数書くことが出来ます。

try:
    f = open(file_name,'w')
    data = dict_input['data']
    f.write(data)
    f.close()
except KeyError:
    print('キーが見つかりませんでした')
except (FileNotFoundError, TypeError) :
    print('ファイルが開けませんでした')
except:
    print('何らかのエラーが発生')

これはファイルをオープンするプログラムの処理の一部ですが、ファイルの入出力は
『Python基礎講座』では説明しないため、細かいコードの説明は省きます。
見て欲しいのはexcept KeyErrorexcept (FileNotFoundError,TypeError)except の部分です。
try句の中で例外が発生した場合、Pythonは初めにその例外がKeyErrorで補足出来るかを確認します。
補足できれば「キーが見つかりませんでした」と出力します。
KeyErrorで捕捉出来なかった場合、FileNotFoundErrorまたはTypeErrorで捕捉出来るかを確認します。
補足できれば「ファイルが開けませんでした」と出力します。
このように複数の例外に対して同一の処理を行うことが可能です。
それでも捕捉出来なかった場合、最後のexcept は『全ての例外』を意味します。分岐のelseと同じ意味を持ちます。
ここまで到達した場合は「何らかのエラーが発生」と出力します。

複数の例外を書かずにexcept : だけ用意すれば全ての例外を捕捉してくれますが、
全ての例外を同じexcept句で捕捉してしまうと、例外発生の原因が分かりにくくなるので、
各例外の種類を書いた上で最後にexcept :を記述すると良いでしょう。

else/finally

elseは「try句内で例外が発生せずに最後まで処理が進んだ場合」処理を実行します。
finallyは「例外の発生に関係なく、最後に」処理を実行します。

try_else_finally.py
try:
    a = 10 / 0
#    a = 10 / 1
    print("{0}".format(a))
except ZeroDivisionError as e:
    print("ZeroDivisionError!!")
else:
    print("else statement")
finally:
    print("finally statement")

上記のプログラムを実行してください。その後、a = 10 / 0 をコメントアウトし、下の a = 10 / 1 のコメントを外して
再度実行し、両者の出力が説明のとおりであることを確認してください。

raise

raiseを使用すると、故意に例外を発生させることが出来ます。
raise 引き起こしたい例外(送出したい例外) を例外を発生させたい箇所に記述し、
except句の中でraise と記述することで、例外を返します。
以下のプログラムを実行後、except句内のraiseをコメントアウトして両者の動作の違いを確認してください。

raise.py
try:
    raise NameError('HiThere')
except NameError:
    print('An exception flew by!')
    raise

例外の説明は以上になります。

次: Python基礎講座(12 関数)