例外処理する仕組み

8.2 例外処理する仕組み

実行時エラー(例外)に対して何も処理を行わないと、プログラムの途中で処理が中断してしまうことが確認できました。実行時エラーに対して適切な処理を行うコードを記述してみましょう。このような処理のことを「例外処理」といいます。

8.2.1 例外処理の基本構文:try except

以下に例外処理の基本的な構文を示します。

書式:例外処理の基本

try except文が読まれると、tryの中の処理が実行され、指定のエラーが発生しなければexcept文はスキップされますが、指定のエラー(例外)が発生した場合、except文が実行されます。
except文の実行後は、try except文以降の処理も実行される仕組みです。

凡例:例外処理の基本

例外処理を記述して2つのブロック(tryとexcept)をつけると、次のような順番で例外が処理されます。

① tryブロック中で例外が起きると、そこで処理を中断し、発生した例外に対応するexceptブロックを探す。
② 例外がexceptブロック内の例外の種類と一致していれば、そのexceptブロック内の処理を行う。
③ exceptブロック内の処理が終わったら、そのtry~exceptブロックの後の処理を続ける。

それでは実際に上記①~③のようになるか、次項のサンプルで確認してみましょう。
前項のサンプルに例外処理を追加したものを紹介します。

8.2.2 配列要素数を超えたアクセスに対して例外処理を行うプログラム

配列にわざと最大配列要素の数を超えて代入を行い、実行時エラーを起こし例外処理が行われることを確認します。

ソース・フォルダー: /Desktop/Python基礎講座
ファイル名: 第8章.ipynb
アクセスURL: http://localhost:8888/notebooks/Desktop/Python基礎講座/第8章.ipynb

    books = ['Java','PHP','Python','Javascript']

    try:
        print(books[4])
    except IndexError:
        print('インデックスエラーの発生')

    print('処理終了')
	

実行結果

	インデックスエラーの発生
	処理終了
	

解説

先ほどと同様、1行目でリスト型変数booksには、インデックス番号0~3の要素が格納されてます。

    books = ['Java','PHP','Python','Javascript']
	

例外が発生しそうな部分の3行目~4行目をtryブロック、例外が発生時の5、6行目にexceptブロックを設けてます。

    try:
        print(books[4])
    except IndexError:
        print('インデックスエラーの発生')
	

4行目で要素数の範囲を超えたインデックス番号の要素にアクセスしようとしているため、例外IndexErrorが発生します。

    print(books[4])
	

5、6行目にてtryブロック内で発生する例外の種類IndexErrorを指定してexceptブロックを設定しているため、4行目で発生した例外を処理できるようになり、処理を途中で中断する代わりに6行目の処理を行います。

    except IndexError:
        print('インデックスエラーの発生')
	

try~exceptブロックの後から処理が再開し、8行目のメッセージを出力していることが確認できます。

    print('処理終了')
	

以下の図に今回のプログラム処理の流れイメージを示します。

図 8.2 1:例外処理の流れイメージ

exceptブロック内の処理が行われ、最後まで問題なく処理が実行されているのが分かります。その結果、エラーに対処したプログラムになっていることが確認できます。
それではもう一つ実行時エラーを確認し、そのエラーに対して例外処理を行うプログラムを紹介します。

8.2.3 0除算に対して例外処理を行わないプログラム

数値を0で除算し実行時エラーをわざと起こすプログラムです。

ソース・フォルダー: /Desktop/Python基礎講座
ファイル名: 第8章.ipynb
アクセスURL: http://localhost:8888/notebooks/Desktop/Python基礎講座/第8章.ipynb

    num = 10/0

    #結果を表示
    print('10/0の結果は',num)
    print('処理終了')
	

実行結果

	---------------------------------------------------------------------------
	ZeroDivisionError                         Traceback (most recent call last)
	<ipython-input-12-ef8b661d5356> in <module>
	----> 1 num = 10/0
	      2 
	      3 #結果を表示
	      4 print('10/0の結果は',num)
	      5 print('処理終了')

	ZeroDivisionError: division by zero
	

解説

プログラミングでは「0で除算」することができないようになっていますが、1行目でわざと0除算をおこなっています。

    num = 10/0
	

実行結果を見ても分かるように、1行目で実行時エラーになり例外が送出されているのが確認できます。
その時に発生する例外は算術計算での例外「ZeroDivisionError」となります。
では次項でこのソースコードに例外処理を追加したサンプルを紹介します。

8.2.4 0除算に対して例外処理を行うプログラム

数値を0で除算し実行時エラーをわざと起こし、例外処理を行うプログラムです。

ソース・フォルダー: /Desktop/Python基礎講座
ファイル名: 第8章.ipynb
アクセスURL: http://localhost:8888/notebooks/Desktop/Python基礎講座/第8章.ipynb

    try:
        #数値をゼロで除算します。
        num = 10/0
        #結果を表示
        print('10/0の結果は',num)
    except ZeroDivisionError:
        print('0で除算はできません。')

    print('処理終了')
	

実行結果

	0で除算はできません。
	処理終了
	

解説

例外が送出される処理(この処理では3行目)に対してtryブロック(1行目~5行目)で囲みます。

    try:
        #数値をゼロで除算します。
        num = 10/0
        #結果を表示
        print('10/0の結果は',num)
	

発生する例外の種類をexceptの直後に記述しています。今回はZeroDivisionErrorとなります。
7行目では例外をキャッチした時に行わせたい、メッセージ表示の処理を行っています。

    except ZeroDivisionError:
        print('0で除算はできません。')
	

実行結果からも確認できるように、前項のサンプルとは違い途中で処理が中断することなく最後まで動作していることが確認できます。

2つのプログラムをもとに例外処理の基本を紹介しましたが、本来わざとエラーを起こすプログラムを組むことはありません。例外処理の基本的な考え方は、作成したプログラムが意図しない原因でプログラムを途中終了させてしまわないようにすることです。基本的にPythonでは、例外が発生する可能性がある箇所では、必ず例外処理を記述しなければいけないことを覚えておきましょう。例外が発生することを想定し例外処理を組み込んでおけば、エラーに強いプログラムを作成できることにもなります。

ポイント

  • 例外が発生する可能性がある箇所には例外処理(try~except)を必ず記述する。そして例外処理を行うことで、エラーに強いプログラムを作成することが可能になる。

8.2.5 例外処理(try~except)の動作について

例外処理を行っていないプログラムは途中で終了してしまうことは理解できたと思います。しかし、正確に言うと少し話が違ってきます。
これまで見てきた例ではクラスや関数を使わないシンプルなプログラムにおいて、「実行時エラー(例外)が起こる処理を記述していると、そのエラーの箇所で途中終了になる」という仕組みを理解しました。
では呼び出し先の関数でエラーが起こるケースを考えてみます。エラーに対する例外処理がその関数やクラス内で見つからない場合には、呼び出し元の関数やクラスに戻って対応する例外処理を探す仕組みになっています。

例外処理を行わないプログラムの流れ

図 8.2 2:例外処理を行わないプログラムの流れ

本章でこれまで紹介したプログラムではクラスや関数を使わないプログラムにおいて例外が発生していましたが、では呼び出し先の関数やクラスで例外が発生した場合、どうなるのでしょうか?
次項では、呼び出し先の関数内で例外が発生した場合の例外処理のプログラムを紹介します。

8.2.6 呼び出した関数内で発生した例外の処理を行わないプログラム

値を0で除算し実行時エラーをわざと起こし、例外処理を行うプログラムです。

ソース・フォルダー: /Desktop/Python基礎講座
ファイル名: 第8章.ipynb
アクセスURL: http://localhost:8888/notebooks/Desktop/Python基礎講座/第8章.ipynb

    def ZeroException():
        num = 10/0
        print('10/0の結果は',num)

    print('処理開始')
    ZeroException()
    print('処理終了')
	

実行結果

	処理開始
	---------------------------------------------------------------------------
	ZeroDivisionError                         Traceback (most recent call last)
	<ipython-input-15-664970673513> in <module>
	      4 
	      5 print('処理開始')
	----> 6 ZeroException()
	      7 print('処理終了')
	      8 

	<ipython-input-15-664970673513> in ZeroException()
	      1 def ZeroException():
	----> 2     num = 10/0
	      3     print('10/0の結果は',num)
	      4 
	      5 print('処理開始')

	ZeroDivisionError: division by zero	
	

解説

ZeroException()メソッド内の処理2行目で0除算を行い、例外をわざと発生させています。

    num = 10/0
	

実行結果を見ると、エラーが表示されている箇所が、ZeroException()メソッドが記載された2行目と、こちらのメソッドの呼び出し元の6行目の2箇所になっているのが確認できます。

	---------------------------------------------------------------------------
	ZeroDivisionError                         Traceback (most recent call last)
	<ipython-input-15-664970673513> in <module>
	      4 
	      5 print('処理開始')
	----> 6 ZeroException()
	      7 print('処理終了')
	      8 

	<ipython-input-15-664970673513> in ZeroException()
	      1 def ZeroException():
	----> 2     num = 10/0
	      3     print('10/0の結果は',num)
	      4 
	      5 print('処理開始')

	ZeroDivisionError: division by zero	
	

① ZeroExceptionメソッドの呼び出し箇所
② 例外が発生している箇所(実際の計算部分)

また5行目の処理が読まれているのに対して7行目の処理が読まれていないことから、5行目が読み込まれ、6行目でZeroExceptionメソッドを呼び出しエラーが発生したことで、ZeroExceptionメソッド内の3行目や、呼び出し元の7行目が読み込まれなかったことが確認できます。

    print('処理開始')
    ZeroException()
    print('処理終了')
	

今回のプログラムの処理の流れを表すと、以下に示す図の流れになります。

図 8.2 3:呼び出し先に例外処理が用意されていない場合の処理の流れ

呼び出し先から例外が発生した場合、呼び出し元へ処理が遡っていますが、本当にそうなのかを確認してみましょう。今回のプログラムの呼び出し元の命令文に例外処理を追加したプログラムを使い次の項で説明を行います。

8.2.7 呼び出したメソッド内で発生した例外の処理を呼び出し元で行うプログラム

ソース・フォルダー: /Desktop/Python基礎講座
ファイル名: 第8章.ipynb
アクセスURL: http://localhost:8888/notebooks/Desktop/Python基礎講座/第8章.ipynb

    def ZeroException():
        num = 10/0
        print('10/0の結果は',num)

    print('処理開始')

    try:
        ZeroException()
    except ZeroDivisionError:
        print('0で除算はできません。')

    print('処理終了')
	

実行結果

	処理開始
	0で除算はできません。
	処理終了
	

解説

ZeroException()メソッド内には特に例外処理は追加していませんが、呼び出し元でtry~except文を記述しています。これによりZeroException ()メソッド内で例外が発生しても呼び出し元で例外を処理することができます。

    try:
        ZeroException()
    except ZeroDivisionError:
        print('0で除算はできません。')
	

今回のプログラムの処理の流れを表すと、以下に示す図の流れになります。

図 8.2 4: 呼び出し元に例外処理が用意されている場合の処理の流れ

0除算を行っている箇所はZeroException()メソッド内で、例外を処理している箇所は呼び出し元と別になっていますが、途中で終了することなく正しく処理が終了しています。この結果から例外の流れが呼び出し先から呼び出し元へ遡っていることが確認できました。
今回呼び出し元で例外を処理しましたが、もちろんZeroException()メソッドで行っても問題ありません。作成するプログラムによっては、例外が発生したときに呼び出し元まで遡られても困るケースもあります。
例外をどこに組み込む必要があるのかは、作成するプログラムによって大きく変わりますので注意してください。

ポイント

  • 例外が発生した場合、例外処理が行われているかのチェックが呼び出し先から呼び出し元へと遡っていく。

8.2.8 例外処理のfinallyブロックについて

例外処理では、例外が発生したメソッド内でexceptブロックが見つからなかった場合に、呼び出し元のメソッドに戻ってexceptブロックが探されることになっています。このような場合、例外の発生に関わらず、そのメソッド内で必ず行っておきたい重要な処理がある場合どうすればいいのでしょうか。その答えは「finallyブロック」を利用することで解決できます。
それでは以下に例外処理のfinallyブロックを追加した構文を示します。

書式:例外処理とfinallyブロック

finallyブロックはexcept文の次に記述します。

凡例:例外処理とfinallyブロック

実行結果

	配列の要素を超えています。
	計算終了
	処理終了
	

finallyブロックを追加することで、tryブロック内で例外の有無に関わらず必ず行わせたい処理を設定することができます。finallyブロックを利用していないと、例外が発生したときに、重要な処理が飛ばされたままプログラムの処理が進んでしまうと困るような場合に大変有用な機能といえます。但し絶対使用しないといけない訳ではないので省略可能になっています。

try~except~finally処理の流れ

try~except~finallyブロック内の、処理の流れを以下の図に示します。

図 8.2 5: try~except~finally処理の流れ

ポイント

  • 例外処理にfinallyブロックをつけると、例外の有無に関わらず必ず処理を行ってくれる。(※省略も可能)

これまでのプログラムではexceptブロックは1つだけ記述していましたが、1つだけではなく複数記述することも可能です。複数記述した場合は上のブロックから順にチェックを行います。また、finallyブロックを記述しているとexceptブロックを省略することもできます。
これまでの例外処理の仕組みをまとめると以下になります。

8.2.9 例外処理のfinallyブロックを利用したプログラム

先ほどの凡例に対して例外が発生しないようソースコードを書き換えた場合のプログラムを介して、例外の発生の有無を問わず、正しくそのfinallyブロック内の処理が行われることを確認します。

ソース・フォルダー: /Desktop/Python基礎講座
ファイル名: 第8章.ipynb
アクセスURL: http://localhost:8888/notebooks/Desktop/Python基礎講座/第8章.ipynb

    books = ['Java','PHP','Python','Javascript']

    try:
        print(books[3])

    except IndexError:
        print('配列の要素を超えています。')

    finally:
        print('計算終了')

    print('処理終了')
	

実行結果

	Javascript
	計算終了
	処理終了
	

解説

先ほどの凡例と異なり、4行目で有効範囲内の配列要素の出力を行ってます。

    try:
        print(books[3])
	

実行結果から確認できますが、例外が発生した場合もしなかった場合もfinallyブロック内で設定した、19行目のメッセージ出力処理が行われていることが確認できます。

    finally:
        print('計算終了')
	

では次の項でexceptブロックを複数使ったプログラムを紹介します。

8.2.10 例外処理に複数のexceptブロックを利用したプログラム

複数のexceptブロックを設定して該当する例外が発生した場合に、正しくそのexceptブロック内の処理が行われることを確認します。

ソース・フォルダー: /Desktop/Python基礎講座
ファイル名: 第8章.ipynb
アクセスURL: http://localhost:8888/notebooks/Desktop/Python基礎講座/第8章.ipynb

    books =range(4)

    try:
        print(books[3])
        num = 10/0
        print('10/0の結果は',num)    

    except IndexError:
        print('配列の要素を超えています。')

    except ZeroDivisionError:
        print('0で除算はできません。')

    finally:
        print('計算終了')

    print('処理終了')
	

実行結果

	3
	0で除算はできません。
	計算終了
	処理終了
	

解説

3行~12行目のようにexceptブロックは複数指定することが可能です。
2つの実行結果からも分かるように、該当する例外が発生すると、その例外クラスを指定しているexceptブロック内の処理が正しく行われていることが確認できます。

    except IndexError:
        print('配列の要素を超えています。')

    except ZeroDivisionError:
        print('0で除算はできません。')
	

例外処理のパターン

これまでのプログラムではいくつかの例外処理のパターンを見てきました。本項までで紹介したプログラムでは以下に示す①~③のパターンは学習しました。例外処理ではexceptブロックを省略することも可能ですが、その際はfinallyブロックを記述する必要があります。例外処理はtryブロックと他のブロック(except、finally)を組み合わせて使用しなければならないルールがあるからです。プログラム例として④は紹介しませんがこのような使い方もあることを覚えておいて下さい。

① try-except(必須)
② try-except-finally(exceptがある場合は任意設定)
③ try-(except*任意の数)-finally(exceptがある場合は任意設定)
④ try-finally(必須)

ポイント

  • 例外処理はexceptブロックを複数記述することができる。逆にexceptブロックは省略も可能だが、tryブロックだけでは例外処理は記述できないのでその際はfinallyブロックを記述する必要がある。

8.2.11 例外エラーの原因を出力する

これまでtry~exceptで処理を行ってきた「例外」とは、例外クラスから生成されたインスタンスそのものです。
exceptのブロック指定した「エラー名」とは、つまり「クラス名」そのものであり、except文を記述する際、そのクラスから生成されたインスタンスそのものを変数に格納することができます。
書式にまとめると以下の通りです。

8.2.12 例外インスタンス情報を出力するサンプル

では例外をわざと発生させ、その際に取得できる例外インスタンスの情報を画面に出力するプログラムを確認していきましょう。

ソース・フォルダー: /Desktop/Python基礎講座
ファイル名: 第8章.ipynb
アクセスURL: http://localhost:8888/notebooks/Desktop/Python基礎講座/第8章.ipynb

    try:
        num = 10/0
        print('10/0の結果は',num)
    except ZeroDivisionError as e:
        print('エラーの原因:',e)

        print('処理終了')
	

実行結果

	エラーの原因: division by zero
	処理終了
	

解説

1~5行目でtry except文を記載しておりますが、2行目の処理が原因でZeroDivisionErrorが発生するため、4行目のexcept文が実行されます。

    try:
        num = 10/0
        print('10/0の結果は',num)
    except ZeroDivisionError as e:
        print('エラーの原因:',e)
	

except文には、「as e」が指定してあり、eにはエラーの原因の情報が格納されます。5行目で変数eを出力しており、エラーの原因として「division by zero」が出力されます。

8.2.13 raise文

例外エラーについて説明してきましたが、Pythonでは、raise文を使うことで意図的に例外エラーを発生することができます。
例外エラーを意図的に発生させる方法(raise文)について説明していきます。

書式:意図的に例外を発生する処理

raise文の書式は以下の通りです。

    raise 例外クラスのエラー名(‘エラーの原因’)
	

raise文を実行すると、指定した例外クラスがそのままエラーとして発生します。丸()内の記載内容に決まりはありませんが、raise文を介してエラーを発生すると、「エラー名:丸括弧()内のメッセージ」がコンソールに表示されるので、エラーの原因について記載するのが良いでしょう。

凡例:raise文でエラーを発生させる処理

    raise IndexError('エラーの原因')
	

実行結果

	---------------------------------------------------------------------------
	IndexError                                Traceback (most recent call last)
	<ipython-input-7-24f05618df32> in <module>
	----> 1 raise IndexError('エラーの原因')

	IndexError: エラーの原因
	

1. try except文とraise文の併用

またraise文はtry except文を併用するのが一般的です。

書式:try except文とraise文の併用

raise文を使ったtry except文の書式は以下の通りです。

    try:
        raise 例外クラス名(‘エラーの原因’)
    except 例外クラス名 as e:
        処理内容
	

凡例:raise文でエラーを発生させる処理

実行結果

	エラーの原因
	

実行結果からraise文の「エラーの原因」が、eに格納されているのがわかります。

図 8.2 6:raise文とtry except文の併用

8.2.14 if文とraise文を併用したプログラム

raise文は例外クラスに該当しない、特定の命令文に対して例外エラーを発生させたい場合に、使用させることが多いようです。
例えば変数に特定の範囲外の値を格納した場合に、エラーを表示させたい場合などに使用したりします。当然、例外エラーに該当しない命令文に対しては、エラーを発生させたくないのでif文と併用するのが一般的です。
以下で、if文とraise文を併用したプログラムを確認していきましょう。

ソース・フォルダー: /Desktop/Python基礎講座
ファイル名: 第8章.ipynb
アクセスURL: http://localhost:8888/notebooks/Desktop/Python基礎講座/第8章.ipynb

    def sampleError(num):
        if num > 5:
            try:
                raise IndexError('値が大きすぎます。')
            except IndexError as e:
                print(e)
        else:
            print('合格')

    sampleError(3)
    sampleError(7)
	

実行結果

	合格
	値が大きすぎます。
	

解説

1~8行目でsampleError()メソッドを記述しております。引数numに設定した値によって、ifで条件分岐しており、5より大きかった場合、try except文が実行され、そうでなかった場合、「合格」が出力されます。

    def sampleError(num):
        if num > 5:
            try:
                raise IndexError('値が大きすぎます。')
            except IndexError as e:
                print(e)
        else:
            print('合格')
	

条件式がtrueだった場合に実行される、try except文について3~6行目で記載されてます。tryブロックの処理では、IndexErrorを指定したraise文が記述されているので、IndexErrorの例外エラーが発生しますが、例外エラーを受け、except文が発生する仕様です。

    try:
        raise IndexError('値が大きすぎます。')
    except IndexError as e:
        print(e)
	

またexcept文では「IndexError as e」を指定しているので、eにはraise文で指定した「’値が大きすぎます。’」が格納され、ブロック内では「print(e)」となっているため、「値が大きすぎます。」が出力されます。
つまりは条件式がtrueだった場合、「値が大きすぎます。」と出力され、falseだった場合「合格」と出力されるメソッドということです。
10、11行目でsampleError()メソッドを2回、呼び出してます。

    sampleError(3)
    sampleError(7)
	

10行目では引数に3を指定しており、「if num > 5」がfalseであるため、「合格」が出力されます。一方、11行目には引数7を指定しており、「if num > 5」がtrueであるため、「値が大きすぎます。」が出力されます。一方、
今回のプログラムについて10行目の処理についてイメージにすると以下の図の通りになります。

図 8.2 7:if文とraise文の併用

また11行目の処理についてイメージにすると以下の図の通りになります。

図 8.2 8: if文とraise文の併用②


NEXT>> 8.3 例外処理の仕組みまとめ