今回は、Python に備わっている例外処理機能を使って、こうしたエラーを処理する方法を学ぶ。
サンプル・プログラム
数値変換時の例外処理
「2.3 入出力とエラー対策」で作った "input2.py" を再び取り上げる。
# 整数に変換する+エラー対策
try:
startInt = int(startStr)
goalInt = int(goalStr)
except Exception as e:
print("エラー > " + str(e))
sys.exit()
例外処理は一般的に、左図のような4つのブロックから成る。
try に続くブロックには、例外が発生するかもしれない処理を書く。このブロックは省略できない。
except には例外の種類と例外変数を記述し、例外発生時の処理を書く。例外の種類と例外変数を省略すると、すべての例外に対してこのブロックを実行する。except ブロック自体は省略可能だが、省略してしまうと例外発生時に何の対策もとらないことになってしまう。
else には例外が発生しなかったとき、すなわち tryブロックが正常に実行されたときに行う処理を書く。省略可能である。
finally には例外の有無に関わらず実行する処理を書く。省略可能である。
try に続くブロックには、例外が発生するかもしれない処理を書く。このブロックは省略できない。
except には例外の種類と例外変数を記述し、例外発生時の処理を書く。例外の種類と例外変数を省略すると、すべての例外に対してこのブロックを実行する。except ブロック自体は省略可能だが、省略してしまうと例外発生時に何の対策もとらないことになってしまう。
else には例外が発生しなかったとき、すなわち tryブロックが正常に実行されたときに行う処理を書く。省略可能である。
finally には例外の有無に関わらず実行する処理を書く。省略可能である。
"input2.py" は except のみがある例外処理である。組み込み関数 int が失敗したときの例外を拾い、エラーメッセージを表示してプログラムを終了(sys.exit())する。
例外の種類は、クラスという形で Python にあらかじめ組み込まれている。主な例外クラスを下図に整理する。
例外の種類は、クラスという形で Python にあらかじめ組み込まれている。主な例外クラスを下図に整理する。
数字を数値に変換する
これまで入力バリデーションに使ってきたユーザー関数 validateNumber を改良しよう。
これまで、バリデーションに失敗したときは、戻り値に False を返すようにしていたが、主要機能である「変換」と関係のない値を返すのは、関数の仕様としてよろしくない。そこで、バリデーションに失敗したときには ValueError例外を発生し、呼び出し側に伝えるよう改良する。あわせて、変換処理を1つの関数の中で完結させ、この関数の中でシステム終了することはしない(必ず呼び出し側に制御を戻す)ようにする。
そこで、ユーザー関数 validateNumber に求める仕様を整理してみる。
これまで、バリデーションに失敗したときは、戻り値に False を返すようにしていたが、主要機能である「変換」と関係のない値を返すのは、関数の仕様としてよろしくない。そこで、バリデーションに失敗したときには ValueError例外を発生し、呼び出し側に伝えるよう改良する。あわせて、変換処理を1つの関数の中で完結させ、この関数の中でシステム終了することはしない(必ず呼び出し側に制御を戻す)ようにする。
そこで、ユーザー関数 validateNumber に求める仕様を整理してみる。
- 引数として渡した数字(str)を整数(int), 浮動小数(float), decimnalのいずれかの数値に変換して戻す。
- 数字以外の文字列が含まれているなど変換に失敗した場合はValueError例外を発生する。
- 引数に最小値や最大値を指定したら、数値がこの範囲内にあるかどうか検査する。範囲外の場合はValueError例外を発生する。
def validateNumber(numStr, type, min=None, max=None, label=""):
"""数字(文字列)を数値に変換する.バリデーション付き.
Args:
numStr(str): 数字(文字列)
type(str): 変換後のデータ型;int/float/decimalのいずれか
min(int/float/decimal, optional): 最小値(省略時はNone)
max(int/float/decimal, optional): 最大値(省略時はNone)
label(str, optional): データ名称(省略時は"")
Returns:
まず、tryブロックの中で、match文を使って場合分けし、引数 type で指定したデータ型への変換を試みる。int, float, decimalのいずれでもない場合は、raise文を使って例外を発生させる。raise文は次のような形式をとる。
exceptブロックでは、変換関数で発生したValueError例外を拾い、raise文を使ってValueError例外にして、日本語のエラーメッセージを付ける。組み込み関数で発生した例外は英語で読みにくいことがあるので、このようにユーザー・プログラム側で翻訳して返す。
elseブロックは変換に成功したときの処理で、仕様にしたがって、変換した数値がこの範囲内にあるかどうか検査する。範囲外の場合はValueError例外を発生する。
なお、ユーザー定義関数 validateNumber は、引数 min, max, labelを省略できるようにしてある。None は数値として意味のない(存在しない)値という意味である。この書き方については、「5.2 ユーザー定義関数」で説明する。
raise 例外の種類(エラーメッセージ)ここでは、仕様どおりにValueErrorを発生させ、type指定が間違っていることをメッセージとして渡す。
exceptブロックでは、変換関数で発生したValueError例外を拾い、raise文を使ってValueError例外にして、日本語のエラーメッセージを付ける。組み込み関数で発生した例外は英語で読みにくいことがあるので、このようにユーザー・プログラム側で翻訳して返す。
elseブロックは変換に成功したときの処理で、仕様にしたがって、変換した数値がこの範囲内にあるかどうか検査する。範囲外の場合はValueError例外を発生する。
なお、ユーザー定義関数 validateNumber は、引数 min, max, labelを省略できるようにしてある。None は数値として意味のない(存在しない)値という意味である。この書き方については、「5.2 ユーザー定義関数」で説明する。
例外処理の応用
上述のユーザー関数 validateNumber はValueError例外のみをキャッチアップしている。これ以外のエラー、たとえばTypeErrorもキャッチアップしたいときは、左図のように exceptブロックを複数書くことで対応できる。
exceptブロックは幾つでも増やすことができるが、冒頭の図「主な例外クラス」に示したように、例外の種類には上下関係がある。たとえば、exceptブロックでArithmeticErrorをキャッチアップすると、その配下にあるOverflowErrorとZeroDivisionErrorも自動的にキャッチアップする。したがって、ArithmeticErrorとOverflowErrorを別々のexceptブロックに書くことは意味がない。
exceptブロックは幾つでも増やすことができるが、冒頭の図「主な例外クラス」に示したように、例外の種類には上下関係がある。たとえば、exceptブロックでArithmeticErrorをキャッチアップすると、その配下にあるOverflowErrorとZeroDivisionErrorも自動的にキャッチアップする。したがって、ArithmeticErrorとOverflowErrorを別々のexceptブロックに書くことは意味がない。
また、たとえば ValueError例外と TypeError例外に対して同じ処理をしたいときは、左図のように exceptをグループ化することができる。
練習問題
次回予告
Python では、平方根や三角関数のような数学関数のほか、キーボードからデータ入力したり画面にデータ出力する機能も関数として、あらかじめ用意されている。また、import文でモジュールを取り込むことで、カレンダー計算やホームページ解析をするための便利な関数を利用できるようになる。さらに、インターネット上には pipコマンドを使ってインストールすると利用できるようになる多くの便利な外部モジュールが公開されている。
次回は、組み込み関数とモジュール(標準ライブラリ、外部ライブラリ)について学ぶ。
次回は、組み込み関数とモジュール(標準ライブラリ、外部ライブラリ)について学ぶ。
コラム:フールプルーフ、フェールセーフ
本文でも触れたが、例外処理は主要機能を実現するためのものではない――主要機能を実現する流れは、if文 や match文を使って制御する――システムの信頼性を担保するために組み込む処理である。
システムの信頼性を担保する手法は、ソフトウェアとハードウェアで異なるが、ソフトウェアに関して言えば、まず、フールプルーフであろう。英語で書くと Fool-Proof――「愚か者に耐える」という意味である。「愚か者」とは、そのソフトウェアを利用するユーザーを指す。
システムの信頼性を担保する手法は、ソフトウェアとハードウェアで異なるが、ソフトウェアに関して言えば、まず、フールプルーフであろう。英語で書くと Fool-Proof――「愚か者に耐える」という意味である。「愚か者」とは、そのソフトウェアを利用するユーザーを指す。
お金を払ってソフトウェアを買ってくれる顧客に対して失礼な言い方ではあるが、事ほどさように、ユーザーは何を入力してくるか分からない。とくに、そのソフトウェアに慣れていない最初のうちは――。
だがしかし、ユーザーが使い始めてすぐ、間違ったデータを入力したことでソフトウェアが暴走したらどう感じるだろうか。そのソフトウェアは駄目だという第一印象を持ってしまうに違いない。そんな印象を与えないようにするのがフールプルーフだ。
だがしかし、ユーザーが使い始めてすぐ、間違ったデータを入力したことでソフトウェアが暴走したらどう感じるだろうか。そのソフトウェアは駄目だという第一印象を持ってしまうに違いない。そんな印象を与えないようにするのがフールプルーフだ。
本文で紹介したユーザー関数 validNumberの例外処理は、数字でない文字を入力したら例外を発生するというものだ。この関数の主要機能とは関係ないし、慣れたユーザーが誤入力することもないだろう。それでも転ばぬ先の杖としてフールプルーフを用意する。
今回は、ユーザー関数 validNumberで例外が発生したら、メインプログラム側でエラーメッセージを表示し、プログラムを終了するようにした。これをフェールセーフ(Fail-Safe)と呼ぶ。システムに障害が発生したら、安全第一でシステムを停止するという考え方だ。
今回は、ユーザー関数 validNumberで例外が発生したら、メインプログラム側でエラーメッセージを表示し、プログラムを終了するようにした。これをフェールセーフ(Fail-Safe)と呼ぶ。システムに障害が発生したら、安全第一でシステムを停止するという考え方だ。
これに対し、障害が発生しても正常に稼動を続けるようにするフォールトトレランス(Fault-Torelance)という考え方がある。ユーザー関数 validNumberで例外が発生したら、再入力を促すのはフォールトトレランスだ。
ハードウェアでは、障害が発生してもシステム全体を止めずに一部の機能を切り離して(または機能を縮退させ)稼動させ続けるという考え方がある。これをフェールソフト(Fail-Soft)と呼ぶ。
(この項おわり)
プログラムの中で対策できるエラーであれば、if文などを使って制御できるが、組み込み関数や、文法そのもののエラーの場合もある。