この記事ではプログラムの実行時に例外(エラー)が発生したときに適切に処理する方法について解説します。プログラムに例外処理を記述しておくことでシステムの品質を高めるとともに、何か問題が起きた時のデバッグも効率化することができます。
目次
例外とは
プログラムでは実行時に発生する、処理が継続不能になったり継続すれば支障をきたすような異常のことを例外と呼びます。例えばPythonでは次のような処理を行うコードを実行した場合に例外が発生します。
- 数値を0で割る
- 定義されていない変数を呼び出す
- 単純な文法ミス
- 配列の範囲外にアクセス
- 引数が不正
本講座をここまで進めてきたあなたはすでにこれらのいくつかを経験しているかと思います。
実際に例外を起こしてみましょう。次のコードは0で割り算をするよう記述されています。
処理の流れがわかりやすくなるように最初の行に「実行開始」、最後に「処理終了」というメッセージを出力するコードを記載しています。
print("実行開始") print(10 / 0) print("処理終了")
このコードを実行してみましょう。
実行開始 Traceback (most recent call last): File "C:\Users\User\Desktop\python_lessons\test.py", line 2, in <module> print(10 / 0) ZeroDivisionError: division by zero
「実行開始」と表示された後、ZeroDivisionErrorというエラー名とその詳細情報が出力されています。このエラーが例外です。
実行時エラーが発生すると処理が中断され、プログラムが強制終了されます。そのため「実行終了」と表示する処理は行われていません。
この時にわかりやすいエラーメッセージを表示したり、別の処理をしたい場合に使えるのが例外処理という仕組みです。
例外処理をしてみよう
例外処理を記述するにはtry文を使って次のように記述します。
try: 例外を補足したいコード except 例外のクラス名 as e: 例外が発生した場合の処理 else: 例外が発生しなかった場合の処理 finally: 例外が発生してもしなくても絶対に行う処理
Pythonでは発生する問題によって例外を使い分けられるようあらかじめたくさんの例外クラスが用意されています。
例外の種類には次のようなものがあります。
- SyntaxError:コードの書き方(構文)が間違っている時
- NameError:未定義の変数や定数を使用したとき
- TypeError:期待と異なる型が渡された時
では実際に先ほどのコードに例外処理を追記してみましょう。
先ほどのコードでは数値を0で割っていたためZeroDivisionErrorが発生していました。これをexceptブロックで補足します。
print("実行開始") try: print(10 / 0) # この部分に例外が発生した場合に補足 except ZeroDivisionError as e: # 補足した例外をe変数に代入 print("0で割ることはできません") # メッセージを出力 print("処理終了")
実行結果は次のようになります。
実行開始 0で割ることはできません 処理終了
「実行開始」と表示された後にtryブロックの中で0で割る処理が行われているためエラーが発生します。
ここでe変数に例外の詳細情報を表すZeroDivisionErrorオブジェクトが代入されます。
そして例外が発生した場合の処理として「0で割ることはできません」と表示しています。このように例外に対応するexceptブロックの部分を例外ハンドラと呼びます。
このようにtryブロック内のコードを実行した際に例外を補足すると、そこで処理を中断してexceptブロックのコードを実行します。
例外処理が実行された後は通常処理に戻るので「処理終了」と表示する処理も実行されています。
try文では例外が発生しなかった場合の処理をelseブロックに、例外が発生してもしなくても必ず実行する処理をfinallyブロックに記述することができます。
実際に先ほどのコードを次のように改変してみましょう。
print("実行開始") try: print(10 / 1) except ZeroDivisionError as e: print("0で割ることはできません") else: print("処理は正常です") # 例外が発生しなかった場合の処理 finally: print("処理終了") # 例外が発生するかどうかにかかわらず実行
このコードでは10を1で割る処理をしているので例外は発生しません。
実行結果は次のようになります。
実行開始 10.0 処理は正常です 処理終了
try文の計算結果として「10.0」と出力された後、例外が発生しなかったのでelseブロックの処理が行われ、「処理は正常です」と表示されています。
また、finallyブロックに記述されている「処理終了」と表示する記述は例外が発生してもしなくても必ず実行されます。
例外情報の詳細を出力
例外が発生した場合にプログラムの処理を中断せずに、例外情報の詳細を出力する方法についても知っておきましょう。
例外情報の詳細を出力するにはtracebackモジュールのformat_excメソッドを使って次のように記述します。
import traceback # tracebackモジュールをインポート print("実行開始") try: print(10 / 0) except ZeroDivisionError as e: print("0で割ることはできません") print(traceback.format_exc()) # 例外情報の詳細を出力 finally: print("処理終了")
tracebackモジュールはスタックトレース(プログラムの実行過程を記録したもの)を取得し表示する機能を提供してくれるモジュールです。
format_excメソッドは例外情報を文字列として取得します。ではこのコードを実行してみましょう。結果は次のようになります。
実行開始 0で割ることはできません Traceback (most recent call last): File "C:\Users\User\Desktop\python_lessons\test.py", line 5, in <module> print(10 / 0) ZeroDivisionError: division by zero 処理終了
エラーの詳細を表示しながらプログラムを中断することなく最後まで実行することができました。
ただこのままではエラーメッセージもエラーの詳細情報も両方とも標準出力されてしまっています。
エラーの詳細情報だけを別のファイルに出力してみましょう。
プログラムの実行環境では多くの場合、標準入力、標準出力に加えて標準エラー出力という機能を持っています。
標準エラー出力を実行するにはsysモジュールのstderrオブジェクトのwriteメソッドを使います。
import traceback, sys # モジュールをインポート print("実行開始") try: print(10 / 0) except ZeroDivisionError as e: print("0で割ることはできません") sys.stderr.write(traceback.format_exc()) # 例外情報の詳細を出力 finally: print("処理終了")
コードはできましたが、コマンドプロンプトでは標準エラー出力も標準出力と同様に画面に表示してしまいます。
次のように実行コマンドにオプションを付けて、標準エラー出力だけを別のファイルにリダイレクトしてみましょう。
コマンド 2> リダイレクト先ファイル名
本講座で使用しているtest.pyで実行するには次のように記述します。
test.py 2> error.txt
この「2」は標準エラー出力であるSTDERRハンドルを表す数値です。
今回はerror.txtファイルにリダイレクトします。なお、ファイルがない場合は自動的に作成されます。
では先ほどのコードをこのコマンドで実行してみましょう。実行結果は次のようになります。
実行開始 0で割ることはできません 処理終了
標準出力のみを出力することができました。
例外の詳細情報を出力したerror.txtを確認してみましょう。
python_lessonsフォルダの中にerror.txtというファイルが作成されているはずです。
標準エラー出力として出力した例外の詳細情報だけが記述されているはずです。
このように実行結果は標準出力で、エラー内容は標準エラー出力に分けて出力することで、画面だけを見ているユーザーは「0で割ることはできません」という分かりやすいメッセージを見ることができ、システム管理者はエラーファイルをチェックすることでエラーの詳細を確認することができます。
複数の例外を補足
ここまではtryブロックの中で発生する例外が1種類だけという前提でコードを書いてきましたが、ある程度複雑なコードブロックになると複数の例外が発生する可能性があります。
次のコードでは計算結果をnumber変数に代入した後で変数を呼び出そうとしていますが、変数名をnunber と打ち間違えてしまっています。
print("実行開始") try: number = 10 / 1 print(nunber) # 打ち間違えてnu n berになっている except ZeroDivisionError as e: print("0で割ることはできません") print("処理終了")
このコードを実行してみましょう。
実行開始 Traceback (most recent call last): File "C:\Users\User\Desktop\python_lessons\test.py", line 4, in <module> print(nunber) NameError: name 'nunber' is not defined
「実行開始」と表示されたあとでNameErrorが発生し、プログラムが強制終了してしまいました。
このコードにはZeroDivisionErrorが発生した場合の処理は書かれていますが、NameErrorが発生した場合の例外処理は記述されていないためです。
このように複数の例外が発生する可能性がある場合はexceptブロックを複数記述することで、それぞれの例外に対して個別に対処することができます。
コードを次のように書き換えてみましょう。
print("実行開始") try: number = 10 / 1 print(nunber) except ZeroDivisionError as e: print("0で割ることはできません") except NameError as e: # NameErrorが発生した場合の処理 print("未定義の変数を呼び出しています") print("処理終了")
exceptブロックを追加してNameErrorが発生した場合の処理を追記しました。
このコードを実行してみましょう。
実行開始 未定義の変数を呼び出しています 処理終了
NameErrorに対応するよう記述した「未定義の変数を呼び出しています」という文章が表示されました。
このように発生した例外によって異なる処理をしたい場合には個別にexceptを定義してもいいのですが、例外が起きた場合の対処が同じ場合、起きる可能性のある例外全てに対応するよう同様の記述をするのは面倒ですし、コードの修正が必要になった場合に修正漏れが起こりやすくなります。
そこで何らかの例外が発生した場合に同様の対処をする場合は次のように、例外クラス名にExceptionと記述します。
try: 補足するコード except Exception as e: # 例外クラスにExceptionと記述 print("予期せぬエラーが発生しました")
Exceptionクラスは全ての例外クラスの親クラスとなっており、全ての例外を補足します。
先ほどPythonにはあらかじめたくさんの例外クラスが用意されていると説明しましたが、Pythonではあらかじめ何か予期しない例外を表すExceptionクラスを親クラスとして、より具体的な例外を表すNameErrorやTypeErrorなどの子クラスを定義されています。
ちなみにExceptionクラスを継承した独自の子クラスを作ることも可能です。
では実際に先ほどのコードの例外処理をExceptionクラスで補足してみましょう。次のように記述します。
print("実行開始") try: number = 10 / 1 print(nunber) except ZeroDivisionError as e: print("0で割ることはできません") except Exception as e: # 上記以外の例外を全て補足 print("予期せぬエラーが発生しました") print("処理終了")
複数の例外を補足する場合、プログラムは上から順番に補足することができるexceptブロックがないかチェックします。
そのため全ての例外を補足するExceptionクラスは一番最後に記述します。
ではコードを実行してみしょう。
実行開始 予期せぬエラーが発生しました 処理終了
発生したNameErrorはZeroDivisionErrorではないため最初のexceptブロックでは補足されず、次のexceptブロックではExceptionクラスが全ての例外を補足するため「予期せぬエラーが発生しました」と表示されました。
このように複数の例外が発生する可能性があり、その処理をまとめたい場合にはExceptionクラスを使うと便利です。