エラーと例外処理

エラーと例外処理

エラーと例外処理

 コンピュータが何らかの理由でプログラムを正しく実行できないと判断して処理を中断してしまうことをエラー (error) とよびます。

 エラーの実体は BaseException とよばれる基底クラスから派生したサブクラスのインスタンスの一群です。システム終了以外のすべてのエラーは Exception クラスから派生しています。

 エラーをその発生段階によって分類すると全体の見通しがよくなります。以下ではエラーを構文エラー (syntax error) と例外 (exception) の 2 種類に分けて解説します。

構文エラー(syntax error)

 スクリプト言語である Python はコードを逐次機械語に翻訳しながら命令を実行していきますが、誤った構文で書かれた箇所を読み込もうとすると機械語への変換ができないので、エラーを告げて処理を中断します。

 このような種類のエラーを 構文エラー (syntax error) とよびます。ほとんどの場合、構文エラーは単純な記述ミスが原因で発生します。たとえば関数の引数を囲む括弧などを付け忘れると SyntaxError が発生し、問題のある箇所を "^" で指し示してくれます。

# 構文エラーの実例

print("Python"
  File "", line 3
    print("Python"
                  ^
SyntaxError: unexpected EOF while parsing

 正しくインデントされていない行があると、Indentation Error (インデントエラー) が発生します。これも構文エラーの一種です。

# インデントエラーの実例

x = 0

# xに1を9回足す
for j in range(10):
x = x + 1
print(x)
 File "", line 5
    x = x + 1
    ^
IndentationError: expected an indented block

 ほとんどのエディタや IDE には自動インデント機能が備わっているので、IndentationError を目にする機会はそれほど多くないはずです。
 

例外 (exception)

 構文に誤りがなくても、プログラム実行中にエラーが発生することもあります。
 この段階で検出されるエラーを 例外 (exception) とよびます。
 たとえば、定義されていない変数を使って演算を実行しようとすると、NameError が発生します。

# NameErrorの実例

print(y + 10)
NameError                                 Traceback (most recent call last)
 in 
      1 
----> 2 print(y + 10)

NameError: name 'y' is not defined

 文字列 (str) と整数 (int) を足し合わせようとすると、データ型が適合しないので、TypeError が発生します。

# TypeErrorの実例

print("10" + 1)
TypeError                                 Traceback (most recent call last)
 in 
      1 
      2 
----> 3 print("10" + 1)

TypeError: can only concatenate str (not "int") to str

 主な組み込み例外の詳細については、以下の記事を参照してください:

 

例外の補足

 例外は常に致命的であるとは限らないので、あるブロックで例外が発生したときに、エラーを告げてプログラムを中断させる代わりに、別の処理を行なってプログラムを続行させたいケースもよくあります。

try ~ except

 ユーザーに 2 つの数値 a, b を入力してもらって、a/b を計算した後に「プログラムを終了します」と表示するプログラムを考えます。よく知られているように、除算にはゼロ除算とよばれるエラーの危険が常にあるので、次のような例外処理を全く考慮しないコードを書いてしまうと、b に 0 が入力された場合、a/b が実行される 6 行目で例外が送出されてプログラムは中断され、残りの部分は実行されません。

# EXC_01

a = input("数値aを入力してください:")
b = input("数値bを入力してください:")
a = float(a)
b = float(b)

print("a/b = {}".format(a/b))
print("プログラムを終了します")
数値aを入力してください:10
数値bを入力してください:0

ZeroDivisionError                         Traceback (most recent call last)
 in 
      4 b = float(b)
      5 
----> 6 print("a/b = {}".format(a/b))
      7 print("a*b = {}".format(a*b))

ZeroDivisionError: float division by zero

 a/b の実行時にゼロ除算エラーが発生した場合には「 0 では除算できません」という文字列を表示して、プログラムを続行させたい場合は以下のようなコードを書きます。

# EXC_02

a = input("数値aを入力してください:")
b = input("数値bを入力してください:")

try:
    a = float(a)
    b = float(b)
    print("a/b = {}".format(a/b))

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

print("プログラムを終了します")
数値aを入力してください:1
数値bを入力してください:0
0では除算できません
プログラムを終了します

 try ~ except で囲まれたブロックで、except の横に記述したエラーが発生した場合には、直ちに except 以下のブロックに制御が移ります (下図)。

 [Python] try except 例外クラス

 たとえば、リスト EXC-2 にあるように、except の横に ZeroDivisionError を添えるとゼロ除算エラーを補足します。

 リスト EXC_02 はゼロ除算には備えていますが、a または b に文字列が入力されると、別の種類の例外が発生してプログラムが中断されてしまいます。

# EXC_03

a = input("数値aを入力してください:")
b = input("数値bを入力してください:")

try:
    a = float(a)
    b = float(b)
    print("a/b = {}".format(a/b))

except ZeroDivisionError:
    print("0では除算できません")
    
print("プログラムを終了します")
数値aを入力してください:Python
数値bを入力してください:3

ValueError                                Traceback (most recent call last)
 in 
      3 
      4 try:
----> 5     a = float(a)
      6     b = float(b)
      7     print("a/b = {}".format(a/b))

ValueError: could not convert string to float: 'Python'

 float(a) で文字列を浮動小数点数に変換できないために ValueError が発生しています。

 複数の例外をまとめて受ける場合は、括弧の中に例外クラスをカンマで区切って記述します。

# EXC_04

a = input("数値aを入力してください:")
b = input("数値bを入力してください:")

try:
    a = float(a)
    b = float(b)
    print("a/b = {}".format(a/b))

except (ZeroDivisionError, ValueError):
    print("不適切な値が入力されました")
    
print("プログラムを終了します")
数値aを入力してください:Python
数値bを入力してください:2
不適切な値が入力されました
プログラムを終了します

 リストEXC_04 はゼロ除算であっても文字列が入力されても「不適切な値が入力されました」と表示するので、ユーザーにとっては何が「不適切」であったのか分かりにくいですね。

 except を重ねてエラーの種類ごとに異なるメッセージを表示したほうが、よりユーザーフレンドリーなプログラムになります。

# EXC_05

a = input("数値aを入力してください:")
b = input("数値bを入力してください:")

try:
    a = float(a)
    b = float(b)
    print("a/b = {}".format(a/b))

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

except ValueError:
    print("ボックスには数値を入力してください")

print("プログラムを終了します")
数値aを入力してください:Python
数値bを入力してください:2
ボックスには数値を入力してください
プログラムを終了します

 

except [例外クラス] as [変数]

 except節で as の後ろに変数を添えて例外を受けることができます:

 except [例外クラス] as [変数]

 たとえば例外クラスに Exception を指定すると、すべての例外を補足しますが、as を使って例外を変数に保存しておくと、発生した例外の種類を確認することができます。

# EXC_06

a = 10
b = 0

try:
    print("a/b = {}".format(a/b))

except Exception as err:
    print("エラーが発生しました!")
    print("エラーの種類: {}".format(err))
エラーが発生しました!
エラーの種類: division by zero

 

finally節

 finally節に書かれたコードは、エラーが発生してもしなくても必ず実行されます。たとえば、ゼロ除算を予測して以下のようなコードを書いたとします。

# EXC_07

a = input("数値aを入力してください:")
b = input("数値bを入力してください:")

try:
    print(a/b)

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

finally:
    print("プログラムを終了します")
数値aを入力してください:Python
数値bを入力してください:3
プログラムを終了します

TypeError                                 Traceback (most recent call last)
 in 
      3 
      4 try:
----> 5     print(a/b)
      6 
      7 except ZeroDivisionError:

TypeError: unsupported operand type(s) for /: 'str' and 'str'

 ユーザーが文字列を入力したので、5 行目でプログラマーにとっては予想外であった TypeError が発生しましたが、それでも「プログラムを終了します」というメッセージは表示されます。
 

raiseによる例外の送出

 raiseキーワードを使うと例外を意図的に発生させることができます。

# EXC_08

raise NameError("変数が定義されていません")
NameError                                 Traceback (most recent call last)
 in 
----> 1 raise NameError("変数が定義されていません")

NameError: 変数が定義されていません

 raise の後ろには例外オブジェクトを添えます (コンストラクタで例外クラスのインスタンスを生成します)。コンストラクタの引数には例外が発生した原因を渡します。