【Python】エラーと例外処理
コンピュータが何らかの理由でプログラムを正しく実行できないと判断して処理を中断してしまうことをエラー (error) とよびます。
エラーの実体は BaseException とよばれる基底クラスから派生したサブクラスのインスタンスの一群です。システム終了以外のすべてのエラーは Exception クラスから派生しています。
エラーをその発生段階によって分類すると全体の見通しがよくなります。以下ではエラーを構文エラー (syntax error) と例外 (exception) の 2 種類に分けて解説します。
構文エラー(syntax error)
スクリプト言語である Python はコードを逐次機械語に翻訳しながら命令を実行していきますが、誤った構文で書かれた箇所を読み込もうとすると機械語への変換ができないので、エラーを告げて処理を中断します。
このような種類のエラーを 構文エラー (syntax error) とよびます。ほとんどの場合、構文エラーは単純な記述ミスが原因で発生します。たとえば関数の引数を囲む括弧などを付け忘れると SyntaxError が発生し、問題のある箇所を "^" で指し示してくれます。
# PYTHON_SYNTAX_ERROR
# In[1]
print("Python"
File "", line 3 print("Python" ^ SyntaxError: unexpected EOF while parsing
正しくインデントされていない行があると、Indentation Error (インデントエラー) が発生します。これも構文エラーの一種です。
# PYTHON_INDENTATION_ERROR
# In[1]
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 が発生します。
# PYTHON_EXCEPTION
# In[1]
print(y + 10)
NameError Traceback (most recent call last)in 1 ----> 2 print(y + 10) NameError: name 'y' is not defined
文字列 (str) と整数 (int) を足し合わせようとすると、データ型が適合しないので、TypeError が発生します。
# In[2]
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 行目で例外が送出されてプログラムは中断され、残りの部分は実行されません。
# PYTHON_TRY_EXCEPT
# In[1]
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 では除算できません」という文字列を表示して、プログラムを続行させたい場合は以下のようなコードを書きます。
# In[2]
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 以下のブロックに制御が移ります (下図)。
たとえば、In[2] にあるように、except の横に ZeroDivisionError を添えるとゼロ除算エラーを補足します。
In[2] はゼロ除算には備えていますが、a または b に文字列が入力されると、別の種類の例外が発生してプログラムが中断されてしまいます。
# In[3]
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 が発生しています。
複数の例外をまとめて受ける場合は、括弧の中に例外クラスをカンマで区切って記述します。
# In[4]
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 不適切な値が入力されました プログラムを終了します
In[4] はゼロ除算であっても文字列が入力されても「不適切な値が入力されました」と表示するので、ユーザーにとっては何が「不適切」であったのか分かりにくいですね。
except を重ねてエラーの種類ごとに異なるメッセージを表示したほうが、よりユーザーフレンドリーなプログラムになります。
# In[5]
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 の後ろに変数を添えて例外を受けることができます:
たとえば例外クラスに Exception を指定すると、すべての例外を補足しますが、as を使って例外を変数に保存しておくと、発生した例外の種類を確認することができます。
# In[6]
a = 10
b = 0
try:
print("a/b = {}".format(a/b))
except Exception as err:
print("エラーが発生しました!")
print("エラーの種類: {}".format(err))
エラーが発生しました! エラーの種類: division by zero
finally節
finally節に書かれたコードは、エラーが発生してもしなくても必ず実行されます。たとえば、ゼロ除算を予測して以下のようなコードを書いたとします。
# In[7]
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キーワードを使うと例外を意図的に発生させることができます。
# PYTHON_RAISE
# In[1]
raise NameError("変数が定義されていません")
NameError Traceback (most recent call last)in ----> 1 raise NameError("変数が定義されていません") NameError: 変数が定義されていません
raise の後ろには例外オブジェクトを添えます (コンストラクタで例外クラスのインスタンスを生成します)。コンストラクタの引数には例外が発生した原因を渡します。
Python 組み込み例外一覧
Python の例外は、BaseException から継承されるサブクラスのインスタンスです。SystemExit, KeyboardInterrupt, GeneratorExit を除くすべての例外クラスは、Exception から継承されています。Python 公式ドキュメントでは、新しい例外クラスを設計する場合には、BaseException ではなく Exception から継承するように推奨しています。
以下に Exception から継承されている主要な組み込み例外をまとめておきます。トレースバックについてはエラーの説明文のみを掲載し、他の部分は省略してあります。
NameError
未定義の変数を参照すると NameError を発生します。
# PYTHON_NAMEERROR
# In[1]
print(x)
NameError: name 'x' is not defined
インポートしていないモジュールを使おうとしたり、未定義の関数を呼び出そうとしたときにも NameError が送出されます。
# In[2]
a = math.sqrt(5)
name 'math' is not defined
TypeError
定義されていない形でオブジェクトを処理しようとすると TypeError が発生します。たとえば、文字列と数値を加えようとする場合です。
# PYTHON_TYPE_ERROR
# In[1]
print("10" + 1)
can only concatenate str (not "int") to str
説明にあるように、文字列 (str) は文字列 (str) との間でのみ + 演算子による結合が定義されています:
print("10" + "1")
101
連続整数を生成する range()関数には整数 (int) しか渡せません。たとえば浮動小数点数 (float) を渡すと TypeError となります。
# In[2]
x = 0
for k in range(10.1):
x += 1
print(x)
TypeError: 'float' object cannot be interpreted as an integer
IndexError
リストやタプルなどのシーケンス型オブジェクトで、範囲を超えるインデックスで要素を参照しようとすると、IndexError が発生します。
# PYTHON_INDEXERROR
# In[1]
# リストを定義
my_list = ["NumPy", "SciPy", "pandas"]
# 4番目の要素を参照
x = my_list[3]
IndexError: list index out of range
KeyError
辞書型オブジェクト (dict) で存在しないキーを参照しようとすると、KeyError を発生します。
# PYTHON_KEYERROR
# In[1]
# 辞書を定義
my_dic = {"日曜日":"晴れ",
"月曜日":"雨",
"火曜日":"曇り"}
# 4番目の要素を参照
x = my_dic["水曜日"]
print(x)
KeyError: '水曜日'
ImportError
モジュールを読み込もうとしたときに問題があると、ImportError を送出します。次のコードはモジュールに存在しない関数を呼び出そうとしてエラーとなる実例です。
# PYTHON_IMPORTERROR
# In[1]
from numpy import myfunc
ImportError: cannot import name 'myfunc' from 'numpy'
ModuleNotFoundError
ModuleNotFoundError は ImportError のサブクラスです。
インポートしようとするモジュール自体が見つからないときに送出されます。
# PYTHON_MODULENOTFOUNDERROR
# In[1]
import nunpy
ModuleNotFoundError: No module named 'nunpy'
StopIteration
イテレータが生成するアイテムがこれ以上ないときに、StopIteration を発生します。
# PYTHON_STOPITERATION
# In[1]
my_list = ["a", "b", "c"]
my_iter = iter(my_list)
for i in range(4):
print(next(my_iter))
a b c StopIteration:
OverflowError
演算結果が許容範囲を超える大きさになると OverflowError となります。
# PYTHON_OVERFLOWERROR
# In[1]
import math
# e=2.718...の1000乗を計算
x = math.exp(1000)
print(x)
OverflowError: math range error
ただし、演算結果が表現できない値になったときの処理方法は関数によって異なります。たとえば、NumPy の指数関数 np.exp() は OverflowError の代わりに無限大を表す浮動小数点数 inf を返します:
# In[2]
import numpy as np
# e=2.718...の1000乗を計算
x = np.exp(1000)
print(x)
inf
ValueError
関数が不適切な引数を受け取ると ValueError を発生します。これは受け取ったオブジェクトの型が適切でないという単純な理由だけでは説明できないこともあります。たとえば、int() に文字列 "Python" を渡すと ValueError を発生します。
# PYTHON_VALUEERROR
# In[1]
a = int("Python")
print(a)
ValueError: invalid literal for int() with base 10: 'Python'
しかし、int() に文字列 "100" を渡してもエラーは送出されずに、int 型オブジェクト 100 を返します。
# In[2]
a = int("100")
print(a)
100
ところが、文字列 "100.5" は ValueError となります。
# In[3]
a = int("100.5")
print(a)
ValueError: invalid literal for int() with base 10: '100.5'
このように同じ型でも受け取った値によって結果が異なるのは、int() が浮動小数点数の端数を処理するだけでなく、文字列で書かれた整数を intオブジェクトに変換するという機能を併せ持つように設計された関数だからです。
AttributeError
オブジェクトに存在しない属性 (変数やメソッド) を参照しようとすると AttributeError を発生します。たとえば、文字列には uper() というメソッドがないので、以下のコードは AttributeError となります。
# PYTHON_ATRIBUTEERROR
# In[1]
x = "Python".uper()
print(x)
AttributeError: 'str' object has no attribute 'uper'
上のコードは文字列を大文字に変換するメソッド upper() を書き間違えたという例です。
【お知らせ】
少しだけ記事をリライトしました。
Jupyter Notebook 用のコード番号 #In[] を添えました。
(古い記事にはコード番号がないので、少しずつ修正中です)
ChatGPTにエラーと例外処理について教えてもらいました
[ChatGPT] プログラムを書くとき、時にはエラーが発生することがあります。エラーは、プログラムが何かを間違って行ったことを教えてくれるメッセージです。例えば、もしプログラムが動物の数を数えるときに、動物が見つからなかった場合、プログラムは「動物が見つかりませんでした」というエラーメッセージを出力します。エラーが発生すると、プログラムは通常停止してしまいます。しかし、Pythonではエラーに対処するための特別な方法があります。それが「例外処理」です。例外処理を使うことで、プログラムがエラーに遭遇しても停止せずに続行することができます。例外処理を使うときは、以下の3つの要素があります:
トライ(Try): プログラムの特定の部分を試してみます。もしエラーが発生した場合、プログラムは停止する代わりに次のステップに進みます。
エクセプト(Except): もしトライの部分でエラーが発生した場合、エクセプトの中に書かれたコードが実行されます。これはエラーを処理するためのコードです。
ファイナリー(Finally): プログラムは最後にファイナリーの中のコードを実行します。エラーが発生したかどうかに関わらず、必ず実行される部分です。