assertで異常時に明示的に例外を発生させ、try~exceptでエラーキャッチする個人的テンプレです。
前提条件
- 動けばソースコードの品質が高い必要はない
- 同じプログラムを長期間、メンテナンスしながら運用する
- 一度の処理に、それなりに時間がかかるので、時間を表示したい
- エラーが起きたら、とにかく止めたい。異常復帰とか必要ない
処理時間を記録する
そこそこ長い処理をする際はかけて放置するわけですが、後で進捗確認する際、どの程度かかったのか知りたいもの。
なので、最近は以下のテンプレを使用しています。
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等に、そのプログラムの制限事項を、記載する
- ソースコード中に、関数レベルの制限事項を、エラーチェック形式で記述する
どちらも、読みやすさも考慮し、過剰にならない程度に入れています。
判定式を用いたエラーチェック
通常であれば、判定式でエラーをチェックし、ユーザーに適切なレスポンスを返し、その後のプログラムの動きを制御します。
例えば以下のような形で。
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を活用することが効果的です。
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によるエラーチェック」「例外処理」を入れたテンプレ。
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なる素晴らしいクラスがあるようなので、色々便利なログ出力を試したい。