[Python入門]例外と例外処理の基礎:Python入門(1/2 ページ)
プログラムの実行を妨げるさまざまな状況や問題を、Pythonでは「例外」という仕組みで表す。その概要と例外を処理する方法を見ていこう。
前回まではクラスについて数回に分けて説明をしてきた。今回と次回はPythonの例外という仕組みについて見ていこう。
例外とは
本連載では、これまでに「エラー(〜例外)が発生した」などという表現が何度も出てきた。Pythonでは、プログラム実行時に発生する問題(エラー)を「例外」(exception)と呼ばれる仕組みを用いて表現する。
エラーにはさまざまな種類がある。例えば、プログラムのインデントが崩れているとか、構文を間違えているといった単純なものもある。以下に示すのは、そうした単純なエラーの例だ。
このようなプログラムを実行するよりも前に発生するエラーのことを「構文エラー」と呼ぶ。
ただし、構文にエラーがなくなって、Pythonのコードを実行できるようになれば、もうエラーが発生しなくなるわけではない。Pythonで「例外」と呼んでいるのは、これらのエラーのことだ。
自分では「完璧」と思って書いたプログラムコードがあったとしても、そのロジックに間違いがあると、それが原因で意図しない状況で意図しない例外が発生して、問題が発現することもある。例えば、特定のクラスのオブジェクトを受け取ることを前提とした関数を定義したが、実際には想定外のクラスのオブジェクトを渡してしまえば、以下のようなエラーが発生するだろう。
さらにはプログラムには問題がなくとも、それ以外の部分を原因としてエラーが発生することもある。例えば、ネットワークでエラーが発生して、必要とするリソースにアクセスできないといったことが考えられる(以下はサンプルで、あえて例外を発生させるためにタイムアウト値を小さく設定している)。
このように、プログラムの実行を妨げる「例外」的な状況はさまざまだが、Pythonでは、それらの状況に応じた「例外クラス」が用意されている。
例外が発生すると、プログラムの実行はそこで中断される。そのため、プログラマーはそれらに対処する必要がある。といっても、プログラムの実行が中断したタイミングで何かを修正するのではなく、最終的には「例外が発生したらどのような対処をするか」をプログラムコードとして記述しておくことで、後に発生する(かもしれない)例外に対処することになる。
例外処理
今述べた「例外が発生したらどのような対処をするかをプログラムコードとして記述しておくこと」を「例外処理」(exception handling)と呼ぶ。
以下では、ユーザーに数値を入力してもらって、それが事前に決めておいた「当たり」の数値と等しいかどうかを確認する「数当てゲーム」を例に例外処理について考えてみよう。そのコードは第11回「while文による繰り返し処理」で見た。以下にそのコードを示す。
import random
answer = random.randint(1, 100)
while True: # 無限ループ
number = int(input('100までの数値を入力してください: '))
if answer < number:
print('もっと小さな数値です')
elif answer > number:
print('もっと大きな数値です')
else:
break # 変数answerの値と変数numberの値が等しければ終了
print('素晴らしい! 正解です!')
このプログラムは「ユーザーが本当に数値(数字列)を入力」している限りは問題なく動作する。だが、数字以外の文字が入力されると、その時点で問題が発生する。恐らくはユーザー入力(input関数の戻り値である文字列)を数値化する時点で例外が発生するだろう。そして、例外処理をしていないので、そこでプログラムの実行は中断される。
上の画像を見ると、ValueError例外が発生し、入力した文字列では10進数整数値に変換できないという意味のメッセージが表示されていることが分かる。
この問題はどのようにすれば解決できるだろうか。ここでは数字以外を含んだ入力が与えられたことが原因なので、「ValueError例外が発生したら、無効な文字列が入力されたので、適切な数を入力してもらうようにユーザーに伝えて、while文の次の繰り返しに進む」ようにすれば、数字以外を含んだ文字列が入力されても処理を滞りなく続けられるはずだ*1。
*1 入力された文字列をあらかじめ検査して(文字列のisdigitメソッドで、その文字列の要素が数字だけで構成されているかどうかを調べられる)、数字以外を含んでいれば、ユーザーに問い合わせをし直す方法もある。
では、これを実際にコードに落とし込んでみよう。Pythonでは例外が発生したときに、その例外を捕まえて(これを「例外を捕捉する」「例外をキャッチする」などと表現する)、その例外に対応する処理を行う(これを「例外処理」と呼ぶ)。これをコードで表現するにはtry文を使用する。
try文の基本的な構文を以下に示す(より詳細な構文はこの後で説明する)。
簡単にまとめると次のようになる。
- try節には例外を発生させる可能性がある文を記述する
- except節では例外が発生した場合に、その例外に対処するコードを記述する
- except節では例外クラスを指定することで、そのexcept節で捕捉する例外を特定できる
ここでは例外が発生する可能性があるのは、int関数による文字列の数値化の部分だ。そして、このときに発生するのは既に見た通り、ValueError例外だった。よって、例外処理を行うコードは次のようになる。
import random
answer = random.randint(1, 100)
while True: # 無限ループ
try:
# 例外を発生させる可能性があるコード
number = int(input('100までの数値を入力してください: '))
except ValueError:
# ValueError例外を処理するコード
print('数字以外が入力されました。数字のみを入力してください')
continue
if answer < number:
print('もっと小さな数値です')
elif answer > number:
print('もっと大きな数値です')
else:
break # 変数answerの値と変数numberの値が等しければ終了
print('素晴らしい! 正解です!')
実行結果を以下に示す。
数字以外を含んだ文字列(上の例では「fifty」)を入力すると、「数字以外が入力されました。数字のみを入力してください」と表示されて、continue文により次の繰り返しが実行されているのが分かる。数字以外を含んだ文字列を数値に変換しようとして、ValueError例外が発生したが、これをうまく処理できているということだ。
このように「try節に例外を発生させるかもしれないコードを記述して、except節で例外を捕捉して処理する」のが、Pythonにおける例外処理の基本構造だ。次に、try文についてもう少し詳しく見ていくことにしよう。
try文
try文の構文を以下に示す(以下ではtry節やexcept節のブロックにはコメントのみが書かれている、つまり、実質的には実行されるコードブロックがないが、実際には各節のブロックには実行可能なコードが必要なことには注意すること)。
try:
# 例外を発生させる可能性があるコード
except 例外クラス1 as 変数名:
# 例外を処理するコード
# 例外クラスを指定した場合、このexcept節では指定されたクラスと
# 互換性のある例外だけを捕捉する。また、「as 変数名」を記述すると、
# 例外に関する情報を含んだオブジェクトに「変数名」でアクセスできる
except 例外クラス2 as 変数名:
# except節は必要なだけ記述できる
except:
# 例外クラスを指定しなかった場合には
# それまでに列挙されなかった(捕捉されなかった)例外が全て捕捉される
# 例外クラスを指定しないExceptionはexcept節の最後に記述すること
else:
# 例外が発生しなかった場合に実行するコード
# ここで例外が発生した場合、上にあるexcept節では捕捉されない
finally:
# 例外が発生してもしなくても最後に実行するコード
上で見たのは、上記の構文でtry節とexcept節を1つだけ記述したものだ。実際には、except節は必要なだけ記述できるし、例外が発生しなかったときにtry文内で実行するコードをelse節に記述することも可能だ。また、例外が発生したか発生しなかったかに関係なく、最後に「クリーンナップ処理」を行うコードをfinally節に記述してもよい。except節のコードのことを「例外ハンドラ」と呼ぶこともある。
except節では例外クラスを指定したり、「as 変数名」として例外に関する情報を含んだオブジェクトを変数に束縛したりもできる。このときには注意することがある。それは「except節は上から順番に調べられていく」ことだ。
例として以下のコードを考えてみよう。これはユーザーの入力に従って、さまざまな例外を発生させるコードだ(発生させない選択肢もある)。「while True:」となって無限ループを続けるので、選択肢「4」を入力するまでは動作を続ける(ここではユーザー入力の数値化の時点で発生する例外も「except ValueError as e」節で捕捉されるようになっている)。
while True:
try:
print()
print('1: ValueError例外を発生')
print('2: IndexError例外を発生')
print('3: 例外を発生させない')
print('4: 終了')
selection = int(input('どれにしますか: '))
if selection == 1:
tmp = int('foo') # ValueError
elif selection == 2:
tmp = 'str'[5] # IndexError
elif selection == 3:
print()
print('例外を発生させませんでした')
elif selection == 4:
print()
print('終了します')
break
else:
print(undefined_var) # 未定義の変数を参照
except ValueError as e:
print('Value Error')
print(e.args)
print()
except IndexError:
print('Index Error')
print()
except Exception as e:
print('Exception')
print(e.args)
print()
print('try文の直後の行を実行しました')
print('無限ループを終了しました')
このコードを実行して、「2」を入力したところを以下に示す。
「2」を入力すると、if文の該当する節で「tmp = 'str'[5]」行が実行される。文字列'str'は3文字なのでインデックス「5」でアクセスできる要素はない。そのため、ここではIndexError例外が発生する。
例外が発生したので、上で述べたようにexcept節が上から調べられ、IndexError例外を処理する例外ハンドラが検索される。最初の「except ValueError as e:」節はValueError例外を処理するための節なので、ここではIndexError例外は捕捉されない。次の「except IndexError:」節で捕捉される。
「except IndexError:」節では「as 変数名」としていないので、例外についての情報を含んだオブジェクトは手に入らない。そのため、このexcept節では「Index Error」と表示するだけの対処をしている。例外処理はこれで終わるが、上の画像では「try文の直後の行を実行しました」と表示されている点に注意しよう。例外処理が終わると、プログラムの実行を妨げる例外的な状況は解消されたものとして「try文の次の文」から実行が続けられるということだ。
興味のある方は、他の選択肢を選んだり、選択肢にはない値や数字以外の文字列なども入力したりして、実行結果を確認しながら、どうしてそうなったのかを考えてみてほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.