Python基礎講座 #19 エラーと例外処理 (Error and Exception)

読了 11分

今日の講座では、エラーと例外処理について勉強していきましょう。 友人にプログラムを一つ作ってあげたとして、その友人がプログラムを実行したら、得体の知れないエラーが出力されてプログラムが終了してしまったら、プログラムを全く知らない友人はかなり戸惑うでしょう。プログラムがエラーを発生させながら異常終了することを、普通プログラムがクラッシュしたと言いますが、これを防ぐために、常にエラーが発生する可能性がある部分には、tryexcept 構文を使って例外処理をしてあげなければなりません。

Pythonの例外処理構文は、次のような基本構造を持っています。

例外処理構文の基本構造
try:
    ...
except:
    ...
else:
    ...
finally:
    ...
キーワード説明
try基本コードが実行されるブロックです
exceptエラーが発生した時に実行されるブロックです。
elseどんなエラーも発生しなかった時に実行されるブロックです。
finally最後に必ず実行されるブロックです。

上の基本構造をもう一度簡単に説明します。

try ブロックには実行したい基本コードを入力すれば大丈夫です。 except ブロックはエラーが発生した時に実行されるブロックです。 else ブロックはどんなエラーも発生しなかった時に実行されるブロックです。 finally ブロックはエラーが発生しても、しなくても、すべてのコードが実行された後に最後に実行されるブロックです。

下のコードはユーザーから二つの数字を入力してもらい、最初の値を二番目の値で割った結果値を出力するプログラムです。実行してみましょう。

Pythonコード
num_1 = int(input('1番目の数字: '))
num_2 = int(input('2番目の数字: '))
result = num_1 / num_2
print('{} / {} = {}'.format(num_1, num_2, result))
コンソール出力
1番目の数字: 4
2番目の数字: 2
4 / 2 = 2.0

4と2を入力して問題なく実行されました。もしユーザーが数字ではなく文字を入力したらどうなるでしょうか?

プログラムを実行して文字「one」を入力してみましょう。

コンソール出力
1番目の数字: one
Traceback (most recent call last):
  File "/Users/curtis/Dev/work/function.py", line 1, in <module>
    num_1 = int(input('1番目の数字: '))
ValueError: invalid literal for int() with base 10: 'one'

はい、当然文字列を整数に変換できないため、エラーを発生させてプログラムが異常終了しました。このようにエラーが発生してプログラムが非正常終了するのを防ぐために、例外処理をしてあげなければならないのですが、基本的な例外処理をするためには tryexcept キーワードを使えば大丈夫です。

それでは実際のコードを作ってみましょう。まず実行したいコードを try ブロックに入れます。次にエラーをキャッチする except キーワードを使います。except キーワードの後にはキャッチしたいエラーオブジェクトの名前を入力します。上のエラーメッセージをよく見ると、ValueError というエラー名が見えますが、このエラー名を6行目の except キーワードの後に入力すれば大丈夫です。そしてこのエラーが発生した時に実行したいコードを except のボディ部分に入力すれば大丈夫です。下のコードを実行してみましょう。

Pythonコード
try:
    num_1 = int(input('1番目の数字: '))
    num_2 = int(input('2番目の数字: '))
    result = num_1 / num_2
    print('{} / {} = {}'.format(num_1, num_2, result))
except ValueError as error:
    print('"ValueError"が発生しました。')
    print(error)
コンソール出力
1番目の数字: one
"ValueError"が発生しました。
invalid literal for int() with base 10: 'one'

エラーは発生しましたが、例外処理がきちんとできて、プログラムがクラッシュせずに正常に終了したのが分かります。

エラーが発生するたびにプログラムを再実行しなければならないとすると、ユーザー側から見るとかなり面倒な作業です。エラーが発生したらもう一度数字を入力できるようコードを修正して実行してみましょう。

Pythonコード
while True:
    try:
        num_1 = int(input('1番目の数字: '))
        num_2 = int(input('2番目の数字: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        break
    except ValueError as error:
        print('入力された値は数字ではありません。もう一度入力してください。')
コンソール出力
1番目の数字: one
入力された値は数字ではありません。もう一度入力してください。
1番目の数字: 4
2番目の数字: 2
4 / 2 = 2.0

今度は ValueError エラーではない別のエラーについて見ていきましょう。初心者プログラマーが最もよくする失敗の一つが、数字を0で割ることです。算数や数学では数字を0で割ると0になりますが、プログラミングで数字を0で割ると ZeroDivisionError というエラーが発生します。0を入力してエラーを発生させてみましょう。

コンソール出力
1番目の数字: 4
2番目の数字: 0
Traceback (most recent call last):
  File "/Users/curtis/Dev/work/function.py", line 5, in <module>
    result = num_1 / num_2
ZeroDivisionError: division by zero

except キーワードは複数回使用可能で、複数のエラーを例外処理することができます。ValueError エラーを処理するコードと同じように ZeroDivisionError エラーを処理するコードを追加して実行してみましょう。

Pythonコード
while True:
    try:
        num_1 = int(input('1番目の数字: '))
        num_2 = int(input('2番目の数字: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        break
    except ValueError as error:
        print('入力された値は数字ではありません。もう一度入力してください。')
    except ZeroDivisionError as error:
        print('0で割ることはできません。もう一度入力してください。')
コンソール出力
1番目の数字: one
入力された値は数字ではありません。もう一度入力してください。
1番目の数字: 4
2番目の数字: 0
0で割ることはできません。もう一度入力してください。
1番目の数字: 4
2番目の数字: 2
4 / 2 = 2.0

2つのエラーがきちんと例外処理されているのが確認できました。

ValueErrorZeroDivisionError はPython組み込み例外オブジェクトの名前であり、この組み込み例外クラスについては、下のPython公式ドキュメントで確認することができます。

参照リンク:Python組み込み例外

上のコードのように、発生するすべてのエラーを予想して例外処理をするのは難しいです。なので、予想されるエラー以外のすべてのエラーについて例外処理をしなければならない場合があります。

別の NameError を発生させるコードを追加して実行してみましょう。

Pythonコード
while True:
    try:
        num_1 = int(input('1番目の数字: '))
        num_2 = int(input('2番目の数字: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        print(bad_variable)  # 存在しない変数を呼び出す
        break
    except ValueError as error:
        print('入力された値は数字ではありません。もう一度入力してください。')
    except ZeroDivisionError as error:
        print('0で割ることはできません。もう一度入力してください。')
コンソール出力
1番目の数字: 4
2番目の数字: 2
4 / 2 = 2.0
Traceback (most recent call last):
  File "/Users/curtis/Dev/work/function.py", line 7, in <module>
    print(bad_variable)
NameError: name 'bad_variable' is not defined

このような場合の問題を解決するために、特定のエラー名を指定せず、すべてのエラーについて例外処理をするコードを追加してみましょう。この時は、例外オブジェクトのベースオブジェクトであるExceptionオブジェクトを使えば大丈夫です。

Pythonコード
while True:
    try:  # 存在しない変数を呼び出す
        num_1 = int(input('1番目の数字: '))
        num_2 = int(input('2番目の数字: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        print(bad_variable)
        break
    except ValueError as error:
        print('入力された値は数字ではありません。もう一度入力してください。')
    except ZeroDivisionError as error:
        print('0で割ることはできません。もう一度入力してください。')
    except Exception:
        print('別のエラーが発生しました。🥲')
        break
コンソール出力
1番目の数字: 4
2番目の数字: 2
4 / 2 = 2.0
別のエラーが発生しました。🥲

Exceptionオブジェクトを使う時は、一番下の部分で使わなければなりません。そうしないと、すべてのエラーがこの部分にキャッチされてしまうからです。一番上に位置を変更して実行してみましょう。

Pythonコード
while True:
    try:  # 存在しない変数を呼び出す
        num_1 = int(input('1番目の数字: '))
        num_2 = int(input('2番目の数字: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        print(bad_variable)
        break
    except Exception:
        print('別のエラーが発生しました。🥲')
        break
    except ValueError as error:
        print('入力された値は数字ではありません。もう一度入力してください。')
    except ZeroDivisionError as error:
        print('0で割ることはできません。もう一度入力してください。')
コンソール出力
1番目の数字: one   
別のエラーが発生しました。🥲

最初の数字に文字列「one」を入力したのに、ValueError ブロックではなく Exception ブロックが実行されたのが分かります。

今度は else キーワードを使ってみましょう。else ブロックは、下のコードのように try ブロックと except ブロックの後に位置すると、else のボディブロックには、どんなエラーも発生しなかった場合に実行したいコードを入れれば大丈夫です。

Pythonコード
while True:
    try:  # 存在しない変数を呼び出す
        num_1 = int(input('1番目の数字: '))
        num_2 = int(input('2番目の数字: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        break
    except ValueError as error:
        print('入力された値は数字ではありません。もう一度入力してください。')
    except ZeroDivisionError as error:
        print('0で割ることはできません。もう一度入力してください。')
    except Exception:
        print('別のエラーが発生しました。🥲')
    else:
        print('どんなエラーも発生しませんでした。')
コンソール出力
1番目の数字: 4
2番目の数字: 2
4 / 2 = 2.0

どんなエラーも発生していないのに else ブロックが実行されませんでした。その理由は、エラーが発生しないと try ブロックの break キーワードが while ループを終了させてしまうからです。break キーワードを else ブロックの中に移して、もう一度実行してみましょう。

Pythonコード
while True:
    try:  # 存在しない変数を呼び出す
        num_1 = int(input('1番目の数字: '))
        num_2 = int(input('2番目の数字: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
    except ValueError as error:
        print('入力された値は数字ではありません。もう一度入力してください。')
    except ZeroDivisionError as error:
        print('0で割ることはできません。もう一度入力してください。')
    except Exception:
        print('別のエラーが発生しました。🥲')
    else:
        print('どんなエラーも発生しませんでした。')
        break
Pythonコード
1番目の数字: 4
2番目の数字: 2
4 / 2 = 2.0
どんなエラーも発生しませんでした

今度はelseブロックがきちんと実行されました。

最後に finally キーワードについて見ていきましょう。finally ブロック内のコードは、エラーが発生しても発生しなくても、最後に必ず実行されます。attempt という変数を while ループの外に定義して、whileループが実行される回数をカウントするようにしましょう。

Pythonコード
attempt = 0

while True:
    try:  # 存在しない変数を呼び出す
        num_1 = int(input('1番目の数字: '))
        num_2 = int(input('2番目の数字: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
    except ValueError as error:
        print('入力された値は数字ではありません。もう一度入力してください。')
    except ZeroDivisionError as error:
        print('0で割ることはできません。もう一度入力してください。')
    except Exception:
        print('別のエラーが発生しました。🥲')
    else:
        print('どんなエラーも発生しませんでした。')
        break
    finally:
        attempt += 1
        print('実行回数: {}'.format(attempt))
コンソール出力
1番目の数字: one
入力された値は数字ではありません。もう一度入力してください。
実行回数: 1
1番目の数字: 4
2番目の数字: 0
0で割ることはできません。もう一度入力してください。
実行回数: 2
1番目の数字: 4
2番目の数字: 2
4 / 2 = 2.0
どんなエラーも発生しませんでした。
実行回数: 3

finally ブロック内のコードが毎回実行されているのが確認できました。

本記事はここまでとし、次回はモジュールとパッケージを取り上げます。

X