Python のエラー処理


こんにちは、切口太郎です。

Python のエラーハンドリングについてお話します。
エラーハンドリングは、Python のプログラムでエラーが発生したときに、そのエラーを検知して対応処理を行うことです。
エラーで何もしないと、Python インタープリターは処理を停止してしまいます。

実際にプログラムを作成して、リリースしたら、途中で Python が動作を停止する障害があるのは相当やばいですよね。

十分テストをすることは重要ですが、想定外のエラーが発生することもあります。

今日はその対応方法についてです。

1 Python のエラーハンドリング


python のようなインタープリター型の言語は、実行時のエラーをプログラム側で補足して、エラーに対応した処理を実行して落ちないプログラムを作成する事ができます。
java のような中間コードコンパイル言語も同じで、プログラムがエラーで中断する(落ちる)事を防ぐことができるようになっています。

この仕様の便利さは絶大で、プログラムを作成する時の負荷(時間や精神的な)がかなり軽減されます。

C言語のようなコンパイル型の言語では、プログラムが中断するような要因を予めチェックする処理を作ってから、本処理を作る必要があります。
Python では、エラーを補足して対応処理を記述できるので、かなりコード量を節約できます。

このようなエラーを補足して対応する処理を行う事を、「例外処理」と呼んでいます。

Python の例外処理は簡単です。

try:
    # ①プログラムの処理

except:
    # ②プログラムのエラーを補足した場合の処理


普通にプログラムを作って、エラーが発生するような要因があれば、

try:

except:

でサンドイッチして、エラー補足後の処理を記述するという、お手軽なコーディングができます。

簡単な例です。
クラスメソッドをインスタンス化しないで、クラス名を指定して、呼び出すと typeerrr が発生します。

class BaseCls:
    def __init__(self, value):
       self._value = value
       _initValue = value

    def getValue(self):
        return self._value
    

try:
    # インスタンス化しないで呼び出とエラーになる!
    print(BaseCls.getValue())                   # エラーになる
except:
    print("インスタンス化してから呼んでください!")

実行すると、15行目のエラーメッセージを出力します。


例外処理の except の書き方は3種類あります。

記述方法
説明
どういう時に使う?
except:
    print("error message")
発生する例外をすべて補足できます。
これ単体で使うことは、ほとんどありません。
下の個別の例外での記述で最後に想定外の例外として利用します。

except TypeError:
    print("type error message")
except FileNotFoundError::
    print("file not found")
発生する例外毎に補足して処理を分けることができます。
一般的な使い方です。
except(TypeError, AttributeError):
    print("class access failed")
複数の例外をまとめて補足します。
補足後の処理が同じ場合に利用します。
例外の処理が同じものは、まとめて記述します。
上の例外の書き方が好きな人と、この書き方が好きな人との派閥があります。

as による例外の内容を取得する

例外の内容を取得できます。主にログを出力したりする時に利用します。

except TypeError as ex:
    print("Catch type error reson for {0}".format(ex))

TypeError の内容を表示します。


2 きちんとした例外処理を書こう!


例外処理を考えていくと、「全部うまく動作したら最後にこの処理をしたい」とか、「エラーが発生しても、終了処理だけはしたい」など例外によって後続処理を分けたい時があります。

Python の例外処理は、このような要求にも対応できます。

else/filnally は、記述しなくても良い(省略可能)です。
必要があれば、記述しましょう!

try:
    # ①プログラムの処理
except:
    # ②プログラムのエラーを補足した場合の処理
else:
     # ③エラーが発生しなかった場合の処理
finally:
    # ④エラーが発生しても、しなくても実行される処理
raise による、例外の意図的な発生を行う
例外の最後に raise による意図的な例外を発生させる方法をご紹介します。

class BaseCls:
    def __init__(self, value):
       self._value = value
       _initValue = value

    def getValue(self):
        return self._value
    

try:
    # インスタンス化しないで呼び出す
    print(BaseCls.getValue())                   # エラーになる
except TypeError as ex:
    print("インスタンス化してから呼び出して下さい ")
    raise

実行すると一度補足した例外を再度発生させます。


インスタンス化してから呼び出して下さい
Traceback (most recent call last):
  File "c:/Users/araya/.vscode/extensions/ms-python.python-2019.5.17517/pythonFiles/ptvsd_launcher.py", line 43, in <module>
    main(ptvsdArgs)
  File "c:/Users/araya/.vscode/extensions/ms-python.python-2019.5.17517/pythonFiles\lib\python\ptvsd\__main__.py", line 434, in main
    run()
  File "c:/Users/araya/.vscode/extensions/ms-python.python-2019.5.17517/pythonFiles\lib\python\ptvsd\__main__.py", line 312, in run_file
    runpy.run_path(target, run_name='__main__')
  File "C:\Users\araya\Anaconda3\lib\runpy.py", line 263, in run_path
    pkg_name=pkg_name, script_name=fname)
  File "C:\Users\araya\Anaconda3\lib\runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "C:\Users\araya\Anaconda3\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "c:\Users\araya\master\Python\trainning\sample06.py", line 13, in <module>
    print(BaseCls.getValue())                   # エラーになる
TypeError: getValue() missing 1 required positional argument: 'self'

メッセージが表示されてから、Pythonで例外を処理しています。


次に、raise の使い方で、補足した例外ではない例外を発生させる処理です。

class BaseCls:
    def __init__(self, value):
       self._value = value
       _initValue = value

    def getValue(self):
        return self._value
    

try:
    # インスタンス化しないで呼び出す
    print(BaseCls.getValue())                   # エラーになる
except TypeError as ex:
    print("インスタンス化してから呼び出して下さい ")
    raise NameError('名前が違います!')

実行するとこうなります。

インスタンス化してから呼び出して下さい
Traceback (most recent call last):
  File "c:\Users\araya\master\Python\trainning\sample06.py", line 13, in <module>
    print(BaseCls.getValue())                   # エラーになる
TypeError: getValue() missing 1 required positional argument: 'self'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:/Users/araya/.vscode/extensions/ms-python.python-2019.5.17517/pythonFiles/ptvsd_launcher.py", line 43, in <module>
    main(ptvsdArgs)
  File "c:/Users/araya/.vscode/extensions/ms-python.python-2019.5.17517/pythonFiles\lib\python\ptvsd\__main__.py", line 434, in main
    run()
  File "c:/Users/araya/.vscode/extensions/ms-python.python-2019.5.17517/pythonFiles\lib\python\ptvsd\__main__.py", line 312, in run_file
    runpy.run_path(target, run_name='__main__')
  File "C:\Users\araya\Anaconda3\lib\runpy.py", line 263, in run_path
    pkg_name=pkg_name, script_name=fname)
  File "C:\Users\araya\Anaconda3\lib\runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "C:\Users\araya\Anaconda3\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "c:\Users\araya\master\Python\trainning\sample06.py", line 16, in <module>
    raise NameError('名前が違います!')
NameError: 名前が違います!


TypeErrorが補足されて、メッセージ "インスタンス化してから呼び出して下さい" を表示した後に、Python のエラー処理で、"NameError: 名前が違います!" に変更できています。

raise を使うかは簡単ですが、どのような時に使うかについては、疑問があると思います。
(java を使う人は、「なんだ、throw 文だろう!」とすぐわかるかも知れまでんが・・・)

初めの例は、メッセージを出力してから例外を再発生させています。
このような使い方は、主にサーバー上でログを出力するための手法です。

2つ目の例は、例外のクラスを変更しています。
これは、この処理を呼び出す元のPython処理が、TypeError ではなく、NameError を想定してコーディングされている場合に、呼び出し元に合わせるために使います。
また、この処理のポリシーとして、「例外は NameError で出力する」と決めている場合、このように例外クラスを変更して再発生させます。

地味ですが、開発の現場ではよく行われる手法なので、「覚えておいて損はない」ぐらいの軽い感じで覚えて下さい。
職業プログラマーになれば、かなり使うので方法なので、指が勝手に覚えてしまいます。


コメント

このブログの人気の投稿

Python のファイルアクセス

Lambda について

Visual Studio Code での Python 開発のポイント