LoginSignup
6
11

More than 5 years have passed since last update.

Pythonでassertとtry~exceptを使った簡易エラー処理

Last updated at Posted at 2018-08-16

assertで異常時に明示的に例外を発生させ、try~exceptでエラーキャッチする個人的テンプレです。

前提条件

  • 動けばソースコードの品質が高い必要はない
  • 同じプログラムを長期間、メンテナンスしながら運用する
  • 一度の処理に、それなりに時間がかかるので、時間を表示したい
  • エラーが起きたら、とにかく止めたい。異常復帰とか必要ない

処理時間を記録する

そこそこ長い処理をする際はかけて放置するわけですが、後で進捗確認する際、どの程度かかったのか知りたいもの。
なので、最近は以下のテンプレを使用しています。

main.py
import time
from datetime import datetime

def main():
    print('Do task.')

def _print_time_msg(msg):
    now = datetime.now().strftime('%Y/%m/%d %H:%M:%S')
    print(f'{now}: {msg}')

if __name__ == '__main__':
    _print_time_msg('start')
    t1 = time.time()

    main()

    t2 = time.time()
    elapsed_time = t2 - t1
    _print_time_msg(f'end[elapsed: {elapsed_time}]')

エラーチェックを入れよう

長期間のメンテナンスを想定しているので、開発者自身であっても、細かい制限事項は忘れていくもの。
ただし、しっかりとしたドキュメントを残す程ではないケース。
私は、以下の形で制限事項を書いています。

  • README等に、そのプログラムの制限事項を、記載する
  • ソースコード中に、関数レベルの制限事項を、エラーチェック形式で記述する

どちらも、読みやすさも考慮し、過剰にならない程度に入れています。

判定式を用いたエラーチェック

通常であれば、判定式でエラーをチェックし、ユーザーに適切なレスポンスを返し、その後のプログラムの動きを制御します。
例えば以下のような形で。

main.py
import os

TRAIN_DIR = './train'

def main():
    if not os.path.exists(TRAIN_DIR):
        print(f'Not exists [{TRAIN_DIR}]!')
        return
    print('Do task.')

assertを用いたエラーチェック

上記コードだと、一つのエラーチェックに対し、「エラー判定式」「ユーザーレスポンス」「エラー後の制御文」が必要になります。
それによって、本筋とは関係のないソースコードが増え、可読性の低下、コーディング効率の低下を招きます。
エラー時はとにかく止めればいい条件下では、assertを活用することが効果的です。

main.py
import os
TRAIN_DIR = './train'

def main():
    assert os.path.exists(TRAIN_DIR), f'Not exists [{TRAIN_DIR}]!'
    print('Do task.')

これで、制御文を使った時同様、フォルダが見つからない時には例外が発生し、以降の処理は実行されません。
制御文を用いる場合に比べたメリットは

  • 「エラー判定式」「ユーザーレスポンス」「エラー後の制御文」が一行で書ける
  • メンテナンス者は、assert内に書かれている条件が、そのプログラムの実行条件だとわかる
  • プログラムの本筋を掴む際、assertは読み飛ばせる

等があります。
誤解のないよう繰り返すと、限定条件下でのメリットなので、制御文を用いる場面と切り分けは必要です。

try~exceptで例外が発生しても後処理をする

assertに引っかかると例外が発生するため、終了時間の記録がされません。
また、想定外エラーでも例外が考えられるので、例外処理を入れましょう。
以上、「処理時間の記録」「assertによるエラーチェック」「例外処理」を入れたテンプレ。

main.py
import os
import time
from datetime import datetime

TRAIN_DIR = './train'

def main():
    assert os.path.exists(TRAIN_DIR), f'Not exists [{TRAIN_DIR}]!'
    print('Do task.')

def _print_time_msg(msg):
    now = datetime.now().strftime('%Y/%m/%d %H:%M:%S')
    print(f'{now}: {msg}')

if __name__ == '__main__':
    try:
        _print_time_msg('start')
        t1 = time.time()

        main()
    except:
       _print_time_msg('exception!')
       import traceback
       traceback.print_exc()
    finally:
        t2 = time.time()
        elapsed_time = t2 - t1
        _print_time_msg(f'end[elapsed: {elapsed_time}]')

例外をtry~exceptで捕まえてしまうと、普段ターミナルからPython実行時に表示される例外メッセージが見れなくなります。
traceback.print_exc()によって、例外メッセージを表示しつつ、処理を続行することができます。

今後

普段はUbuntuサーバのDockerコンテナにTeraTermで繋いで作業しているのですが、この方法だと、コンテナを切断してしまうと、実行履歴が見れなくなってしまいます。
実行ログをファイル出力したいですが、printによるターミナル出力も捨てられない。
そのようなクラス、以前C#では自作しましたが、PythonにはLoggerなる素晴らしいクラスがあるようなので、色々便利なログ出力を試したい。

6
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
11