Pythonの例外処理【プログラミング初心者向け教材】

プログラミング

プログラムにおいて処理を続行することが不可能なエラーが発生した場合には、例外処理を行います。

例外処理は、本来実行したい処理とは異なる部分ですが、全く記述しなかったり、不適切に記述すると、エラー発生時にシステムが停止してしまったり、必要な情報を残すことができなかったりするなどの重大な事態を招いてしまう恐れがあります。

ですから、実際の開発現場でプログラミングをするには、必須の処理となります。

本記事では、Pythonにおける例外処理についてまとめます。

プログラミング初心者の方の学習や、忘れてしまった方の復習として、参考にしていただければ幸いです。

記載しているプログラムは、Python3.6.8を使って動作確認をしています。

Pythonの例外処理

pythonの例外処理に関連する処理は以下のとおりです。

  • try…except文:try節内で発生した例外をexceptで補足する。
  • raise文:プログラマーが意図的に例外処理を発生させる。
  • else文:try節で例外が発生しなかった場合に実行する処理。try節で例外が発生した場合は処理されない。
  • finally文:例外が発生してもしなくても際にも必ず実行される処理。

try…except文

例外が発生する可能性がある文をtry節に記述します。

例外が発生すると、try節の例外発生以降の処理はスキップされ、except節の中が実行されます。

def main():
    try:
        print("before exception.")
        raiseException()
        print("after exception.")
    except Exception as e:
        print(e)

上記の関数mainでは、raiseExceptionという関数を呼び出し、そこでは必ず例外が発生するとします。

例外が発生すると、それ以降の処理はスキップして、except節の処理が実行されます。

つまり、上記のプログラムでは、”before exception.”が表示された後に、”after exception.”は表示されず、except節のprint(e)の処理が実行されます。

except文には補足するクラスを指定します。複数種類の例外が発生する可能性がある場合には、いくつも指定することができます。

except節の処理が異なる場合には、except文を複数記述します。

def main():
    try:
        print("before exception.")
        raiseException()
        print("after exception.")
    except SyntaxError as e:
        print("SyntaxError.")
    except Exception as e:
        print("Exception.")

except節の処理が同じである場合には、1つのexcept文に複数の捕捉対象のクラスを指定します。

def main():
    try:
        print("before exception.")
        raiseException()
        print("after exception.")
    except (SyntaxError, Exception) as e:
        print(e)

なお、except文に例外クラスを指定しない場合には、すべての例外を捕捉するのですが、公式ドキュメントにもあるとおり、あまりおすすめしません。

except 節では例外名を省いて、ワイルドカード (wildcard、総称記号) にすることができます。
ワイルドカードの except 節は非常に注意して使ってください。
というのは、ワイルドカードは通常のプログラムエラーをたやすく隠してしまうからです!
引用:8. エラーと例外ーPython 3.6.8 ドキュメント

raise文

raise文を使うことで、プログラマーは任意の場所で例外を発生させることができます。

def raiseException():
    print("before raise.")
    raise Exception("Test Exception.")
    print("after raise")

上記の関数raiseExceptionでは、この関数が呼び出されると必ず例外が発生します。

例外が発生すると、それ以降の処理はスキップして、この関数を抜けて呼び出し元の処理に戻ります。

つまり、上記の例では、”before raise.”が表示された後に、”after raise.”は表示されず、この関数を抜けます。

else文

try…except文には、else文を指定することができます。

else文を指定すると、try節の中で例外が発生しなかった場合に、else節の処理を実行することができます。

def main():
    try:
        print("before exception.")
        raiseException() # -> 例外発生せず
        print("after exception.")
    except Exception as e:
        print(e)
    else:
        print("not exception.")

上記の例では、”before exception.”が表示された後に、例外が発生せず、”after exception.”が表示され、その後else節の処理が実行されて、”not exception.” が表示されます。

finally文

finally文を使うと、例外が発生してもしなくても、どちらの場合でも必ず処理を実行させることができます。

def main():
    try:
        print("before exception.")
        raiseException()
        print("after exception.")
    except Exception as e:
        print("catch exception.")
    else:
        print("not exception.")
    finally:
        print("finally.")

上記の例では、例外が発生しない場合は、”before exception.” → “after exception.” → “not exceptin.” → “finally.”の順に表示され、try節、else節の後に、finally節が処理されます。

例外が発生した場合は、”before exception.”→”catch ecxeption.”→”finally.”の順に表示され、try節、except節の後に、finally節が処理されます。

finallyでは、通常例外が発生しても、しなくても必ず必要となる、リソース開放などの事後処理を記述することが多いです。

プログラムの構成によっては、except節は記述せずに、try…finally文だけ記述する場合もあります。

エラーオブジェクト

except文では、例外情報として例外オブジェクトを補足することができます。

def main():
    try:
        print("before exception.")
        raiseException()
        print("after exception.")
    except Exception as error:
        print(error)

上記の例では、errorという変数に補足したオブジェクトを格納し、コンソールにerrorが持つメッセージを表示しています。

except文で指定したクラスの例外を捕捉しますが、いずれにも該当しない例外クラスの場合は、上位にraiseされます。

逆に、自分でraiseする場合には、任意の型のオブジェクトを例外情報として指定することができます。

def raiseException():
    raise Exception("Test Exception.")

上記の例では、Pythonが標準で提供している、Exceptionクラスを使用しています。

エラーの種類を区別するために、Pythonでは組込みオブジェクトとして標準でいくつかの例外オブジェクトを提供しています。下記はその一部です。

  • ImportError:import文でモジュールをロードした時に問題が発生した場合のエラー
  • IndexError:指定したインデックスが範囲外である場合
  • SyntaxError:構文エラーや文法誤りがあった場合のエラー
  • TypeError:演算や関数が適切でない型のオブジェクトに対して適用された場合のエラー

これらを自分でraiseする時に、指定することもできます。

適切なエラーの種類がない場合は、独自の例外オブジェクトを作成することも可能です。

開発現場によっては、独自の例外オブジェクトを用意していて、発生したケースに応じて使い分けたりします。

独自の例外オブジェクトを作成する際には、基本的にExceptionオブジェクトを継承して作成することが多いです。

ログ出力

例外が発生した際に、後からなぜ発生したのかを調査する際に重要となるのがログです。

開発中はデバッグなどにより原因を特定することができますが、本番運用が始まるとデバッグなどはできないので、ログだけが頼りになります。

原因を特定するためには、原因が分かるようなログを出力する必要があります。

その1つがスタックトレースです。

スタックトレースは、プログラムを処理してきた順序(関数の呼び出し階層)が記録されていて、どこで例外が発生したのかを特定するために必要な情報となります。

Pythonでスタックトレースを出力させるには、tracebackモジュールを使うこと多いです。

import traceback
def main():
    try:
        print("before exception.")
        raiseException()
        print("after exception.")
    except Exception as e:
        print(traceback.format_exc())

上記のプログラムでは、except節でtracebackのformat_excを呼び出してスタックトレースを表示しています。

スタックトレースには以下のような情報が表示され、どのプログラムの何行目で例外が発生したのかを特定することができます。

Traceback (most recent call last):
  File "exception_python.py", line 6, in main
    raiseException()
  File "exception_python.py", line 17, in raiseException
    raise Exception("Test Exception.")
Exception: Test Exception.

例外発生時のログ出力には、基本的にこのスタックトレースとともに、他にデータを特定できる情報(レコードのIDなど)や引数の情報(不正なデータが渡されていないか)などを出力することが多いかと思います。

どんな情報をログ出力すべきかは、経験によるところもあるのですが、自分が調査する立場ならどんな情報が必要となりそうかを考えてみると良いと思います。

例外処理は障害発生の最後の砦

プログラムは必ずと言ってよいほどエラーが発生します。

プログラム自体が完璧であったとしても、連携しているデータベースやネットワーク、OSなどあらゆるところで想定しないエラーが発生します。

プログラマーとしては、あらゆる事態を想定し、エラーが発生してもなるべく影響を少なくし、原因調査のために必要な情報を残すという点を心がける必要があります。

僕個人としては、正常系のプログラムであれば誰でも書けるが、この例外処理を無駄なく適切に書けるプログラマーが良いプログラマーであると思っています。

今回は、Pythonの例外処理についてまとめました。

参考になれば幸いです。

コメント

タイトルとURLをコピーしました