例外クラスを自作して使用する Python コード例を書きました。
Python マニュアルによると、自作の例外は ユーザー定義例外 (User-defined Exceptions) というようでした。
(Python) ユーザー定義例外 (User-defined Exceptions)
例外クラスの書き方は決まりきっていたので、とても簡単でした。
- class と書いて、
- エラー名をつけて、
- カッコの中に Exception
と書くだけです。
クラスの中身は無くてOKでしたので、pass と書きます。
# 例外クラスを作ります。
class JisakuError(Exception):
"""自作プログラムのエラーを知らせる例外クラスです。"""
pass
# 例外クラスを使います。
raise JisakuError(f'数値がほしいのに None が来ました。')
実行結果です。
Traceback (most recent call last):
File "***/app_main.py", line 7, in <module>
raise JisakuError(f'数値がほしいのに None が来ました。')
__main__.JisakuError: 数値がほしいのに None が来ました。
狙って例外を発生させるときは、自作の例外クラスを作って、それを raise 文に渡すのがおすすめです。
例外クラスを print()
関数のようにして使うことで、オリジナルのエラーメッセージを載せた例外を発生させることができました。
もちろん、Python 組み込みの ValueError()
などにエラーメッセージを渡すこともできました。
しかしながら、例外クラスを自作したところ、ログファイルやコンソールがとても見やすくなりました。
長く使うつもりのコードを書いているときに、とても役に立ちました。
自作の例外クラスを作るコード例
Python マニュアルに、例外クラスの作り方が載っていました。
エラー名でクラスを作って、組み込みの Exception を継承するだけで OK でした。
あとは、適当なエラーメッセージを用意して、raise 文と一緒に使うだけです。
試しに JisakuError という例外を作ってみました。
"""自作の例外クラスを作る Python コード例です。"""
# (1/2) 例外クラスを作ります。
class JisakuError(Exception):
"""自作プログラムのエラーを知らせる例外クラスです。"""
pass
# (2/2) 例外クラスを使います。
def main():
# なにか予想外の値が来ました。
value = None
# Value が None だったら自作例外を出してみます。
if value is None:
raise JisakuError(f'None じゃダメなのに {value} が来ました。')
return
if __name__ == '__main__':
main()
実行結果
実行結果の画面表示です。
Traceback (most recent call last):
File "***/app_main.py", line 20, in <module>
main()
File "***/app_main.py", line 15, in main
raise JisakuError(f'None じゃダメなのに {value} が来ました。')
__main__.JisakuError: None じゃダメなのに None が来ました。
JisakuError というエラーが出て、raise 文のところで止まりました。
try 文を使わなかったので、これが正常な動作になります。
『変数の値をチェックして自作例外を出す』というのは、よく書く定番の処理でした。
"""docstring""" か pass のどちらか1つで OK でした
自作の例外クラスの中身は、"""docstring"""
か pass
のどちらか1つで OK でした。
(Python) クラスオブジェクト (__doc__
, docstring)
(Python) docstring
(用語集)
(Python) pass
文(チュートリアル)
(Python) pass
文(言語リファレンス)
なので、2行で書くこともできました。
(Python) 例外を処理する(例外クラスを短く書いていた例)
外部ライブラリをいくつか見てきましたが、『エラー名』と『ドックストリング』の2行で書いているコードも見かけましたし、『エラー名』と『pass 文』の2行で書いているコードも見かけました。
# [A] docstring と pass で書いた場合です。
class JisakuErrorA(Exception):
"""自作プログラムのエラーを知らせる例外クラスです。"""
pass
# [B] docstring で書いた場合。
class JisakuErrorB(Exception):
"""自作プログラムのエラーを知らせる例外クラスです。"""
# [C] pass で書いた場合。
class JisakuErrorC(Exception):
pass
# [D] なにがなんでも行数を減らしたい場合。
class JisakuErrorD(Exception): pass
# [X] 文字数まで削りたい場合。
class X(Exception):0
# (デバッグ) どれでも例外クラスとして機能しました。
import traceback
print('start\n')
try:
raise JisakuErrorA('エラーA (3行)')
except JisakuErrorA as e:
print(traceback.format_exc(limit=0))
try:
raise JisakuErrorB('エラーB (docstring)')
except JisakuErrorB as e:
print(traceback.format_exc(limit=0))
try:
raise JisakuErrorC('エラーC (pass)')
except JisakuErrorC as e:
print(traceback.format_exc(limit=0))
try:
raise JisakuErrorD('エラーD (1行)')
except JisakuErrorD as e:
print(traceback.format_exc(limit=0))
try:
raise X('エラーX (たぶん最少の文字数)')
except X as e:
print(traceback.format_exc(limit=0))
print('end')
実行結果の画面表示です。
start
Traceback (most recent call last):
JisakuErrorA: エラーA (3行)
Traceback (most recent call last):
JisakuErrorB: エラーB (docstring)
Traceback (most recent call last):
JisakuErrorC: エラーC (pass)
Traceback (most recent call last):
JisakuErrorD: エラーD (1行)
Traceback (most recent call last):
X: エラーX (たぶん最少の文字数)
end
簡単な例外クラスで十分に役立った
ほんの3行の例外クラスでしたが、自作の例外を使ったことで、プログラムのログがとても見やすくなりました。
画面表示やログファイルをチェックしているときに、これは自分が意図的に起こしたエラーだと、ひと目でわかるようになりました。
ところで、自作の例外クラスには、ほかにもいろいろな機能が追加できるようでした。
しかしながら、個人的にはそこまで色々必要になったケースはなかったです。
Python マニュアルにも『通常は単純なものに (usually kept simple)』とありましたし、実際にそれで十分役に立っています。
例外クラスでは、普通のクラスができることなら何でも定義することができますが、通常は単純なものにしておきます。
自作例外に限らず、ロギングでもそうでしたが、あまり細かく『例外』や『ロガー』を作り込んでも『時間ばかり掛かって仕方ない』となりました。
簡単な例外クラスでも、ログが見やすくなって十分に便利でした。
エラーメッセージを取得するコード例
例外の内容は、例外インスタンス e
から取得することもできましたし、traceback モジュールを使用して取得することもできました。
エラーメッセージだけを取得したり、例外の名前(エラーの名前)だけを取得したりすることができました。
エラーメッセージはログファイルに記録しておくと便利でした。
自作の例外をキャッチして、エラーメッセージをログに記録するコード例です。
"""
自作例外のエラーメッセージを取得して、
ログに出力する Python コード例です。
"""
import logging, traceback, pathlib
# (1/7) 自作の例外を定義します。
class JisakuError(Exception):
"""自作プログラムのエラーを知らせる例外クラスです。"""
pass
# (2/7) 名前付きロガーを取得します。
logger = logging.getLogger(__name__)
# 「名前付きロガー」取得時のロギングレベルは NOTSET (0) です。
def main():
"""メイン関数です。"""
# (3/7) ルートロガーを取得します。
# 「ルートロガー」取得時のロギングレベルは WARNING (30) です。
root = logging.getLogger()
root.setLevel(logging.DEBUG)
# (4/7) ルートロガーにストリームハンドラを追加します。
# どのハンドラも生成時のロギングレベルは NOTSET (0) です。
sh = logging.StreamHandler()
sh.setLevel(logging.DEBUG)
sh.setFormatter(logging.Formatter('%(name)s %(levelname)s: %(message)s'))
root.addHandler(sh)
# (5/7) ルートロガーにファイルハンドラを追加します。
# どのハンドラも生成時のロギングレベルは NOTSET (0) です。
log_txt = pathlib.Path(r'F:\apps\data\log.txt')
fh = logging.FileHandler(log_txt, mode='a', encoding='utf-8')
fh.setLevel(logging.INFO)
fh.setFormatter(logging.Formatter('%(name)s %(levelname)s: %(message)s'))
root.addHandler(fh)
# (6/7) 試しに raise 文で例外を出してみます。
root.debug('start')
root.info('%(name)s %(levelname)s: %(message)s')
# なにか予想外の値が来ました。
value = None
try:
if value is None:
raise JisakuError(f'[予想外] None じゃダメなのに {value} が来ました。')
except JisakuError as e:
# (7/7) 名前付きロガーでエラーを記録してみます。
logger.info('■ 例外インスタンスの e からエラー情報を取得します。')
logger.info(f'(e) {e}')
logger.info(f'(e.args) {e.args}')
logger.info(f'(e.__doc__) {e.__doc__}')
logger.info(f'(e.__class__) {e.__class__}')
logger.info(f'(e.__class__.__name__) {e.__class__.__name__}\n')
# (補足) 名前付きロガーのログが、ルートロガーに伝わり、
# ルートロガーに取り付けたハンドラで記録されます。
# (ルートロガーへの伝搬は logger.propagate 属性で制御します)
logger.info('■ トレースバック全体を取得します。')
logger.info('### traceback.format_exc() ###')
logger.info(traceback.format_exc())
logger.info('■ 引数 limit=0 を指定したら、例外部分だけを取得できました。')
logger.info('### traceback.format_exc(0) ###')
logger.info(traceback.format_exc(0))
logger.info('■ トレースバックの最後の行だけのリストを取得します。')
logger.info('### traceback.format_exception_only(type(e), e) ###')
logger.info(traceback.format_exception_only(type(e), e))
logger.info('■ リストをスター * でアンパックしてロガーに渡します。')
logger.info('### *traceback.format_exception_only(type(e), e) ###')
logger.info(*traceback.format_exception_only(type(e), e))
logger.info('■ リストを str.join() で連結してロガーに渡します。')
logger.info("### ''.join(traceback.format_exception_only(type(e), e)) ###")
logger.info(''.join(traceback.format_exception_only(type(e), e)))
root.debug('end')
# もちろん、ルートロガーでログを記録しても OK です。
return
if __name__ == '__main__':
main()
実行結果
実行結果の画面表示です(StreamHandler
の標準エラー出力 sys.stderr
)。
root DEBUG: start
root INFO: %(name)s %(levelname)s: %(message)s
__main__ INFO: ■ 例外インスタンスの e からエラー情報を取得します。
__main__ INFO: (e) [予想外] None じゃダメなのに None が来ました。
__main__ INFO: (e.args) ('[予想外] None じゃダメなのに None が来ました。',)
__main__ INFO: (e.__doc__) 自作プログラムのエラーを知らせる例外クラスです。
__main__ INFO: (e.__class__) <class '__main__.JisakuError'>
__main__ INFO: (e.__class__.__name__) JisakuError
__main__ INFO: ■ トレースバック全体を取得します。
__main__ INFO: ### traceback.format_exc() ###
__main__ INFO: Traceback (most recent call last):
File "***/app_main.py", line 43, in main
raise JisakuError(f'[予想外] None じゃダメなのに {value} が来ました。')
JisakuError: [予想外] None じゃダメなのに None が来ました。
__main__ INFO: ■ 引数 limit=0 を指定したら、例外部分だけを取得できました。
__main__ INFO: ### traceback.format_exc(0) ###
__main__ INFO: Traceback (most recent call last):
JisakuError: [予想外] None じゃダメなのに None が来ました。
__main__ INFO: ■ トレースバックの最後の行だけのリストを取得します。
__main__ INFO: ### traceback.format_exception_only(type(e), e) ###
__main__ INFO: ['JisakuError: [予想外] None じゃダメなのに None が来ました。\n']
__main__ INFO: ■ リストをスター * でアンパックしてロガーに渡します。
__main__ INFO: ### *traceback.format_exception_only(type(e), e) ###
__main__ INFO: JisakuError: [予想外] None じゃダメなのに None が来ました。
__main__ INFO: ■ リストを str.join() で連結してロガーに渡します。
__main__ INFO: ### ''.join(traceback.format_exception_only(type(e), e)) ###
__main__ INFO: JisakuError: [予想外] None じゃダメなのに None が来ました。
root DEBUG: end
ログファイル (log.txt) の内容です。
root INFO: %(name)s %(levelname)s: %(message)s
__main__ INFO: ■ 例外インスタンスの e からエラー情報を取得します。
__main__ INFO: (e) [予想外] None じゃダメなのに None が来ました。
__main__ INFO: (e.args) ('[予想外] None じゃダメなのに None が来ました。',)
__main__ INFO: (e.__doc__) 自作プログラムのエラーを知らせる例外クラスです。
__main__ INFO: (e.__class__) <class '__main__.JisakuError'>
__main__ INFO: (e.__class__.__name__) JisakuError
__main__ INFO: ■ トレースバック全体を取得します。
__main__ INFO: ### traceback.format_exc() ###
__main__ INFO: Traceback (most recent call last):
File "***/app_main.py", line 43, in main
raise JisakuError(f'[予想外] None じゃダメなのに {value} が来ました。')
JisakuError: [予想外] None じゃダメなのに None が来ました。
__main__ INFO: ■ 引数 limit=0 を指定したら、例外部分だけを取得できました。
__main__ INFO: ### traceback.format_exc(0) ###
__main__ INFO: Traceback (most recent call last):
JisakuError: [予想外] None じゃダメなのに None が来ました。
__main__ INFO: ■ トレースバックの最後の行だけのリストを取得します。
__main__ INFO: ### traceback.format_exception_only(type(e), e) ###
__main__ INFO: ['JisakuError: [予想外] None じゃダメなのに None が来ました。\n']
__main__ INFO: ■ リストをスター * でアンパックしてロガーに渡します。
__main__ INFO: ### *traceback.format_exception_only(type(e), e) ###
__main__ INFO: JisakuError: [予想外] None じゃダメなのに None が来ました。
__main__ INFO: ■ リストを str.join() で連結してロガーに渡します。
__main__ INFO: ### ''.join(traceback.format_exception_only(type(e), e)) ###
__main__ INFO: JisakuError: [予想外] None じゃダメなのに None が来ました。
自作例外のエラー名だけを取得することもできましたし、最後の行だけ(エラーメッセージの行だけ)を取得することもできました(ログファイルを小さく保つためによく使用しています)。
もちろん、エラーメッセージ全体(トレースバック全体)を取得することもできました。
traceback モジュールでエラーメッセージを取得する方法を書きました。
⇒ Python で例外のエラーメッセージを取得するコード例
logging モジュールでロガーを使う方法を書きました。
concurrent.futures
でログを記録するコード例。
multiprocessing
でログを記録するコード例。
Python マニュアル
コード例で使用した Python 機能のマニュアルの場所です。
try 文 と例外クラス
(Python) e.args
例外インスタンス e
の .args
属性は、例外の基底クラス BaseException
の属性でした。
(Python) 例外のクラス階層 try 文の execpt 節で、例外クラスを書く順番を決めるときに、時々見ています。
特殊属性
(Python) 型とメンバー(特殊属性のリスト)__doc__, __name__, ...
トレースバック
(Python) traceback.format_exc(limit=None, chain=True)
引数 limit=
の説明は、以下の場所にありました。
(Python) traceback.print_tb(tb, limit=None, file=None)
(引数 limit の説明)
(Python) traceback.format_exception_only(exc, /[, value])
なぜ最初の引数に type(e)
を渡す設計なのか不思議に思っていたのですが、いつのバージョンからか、例外インスタンスの e
だけで OK になっているようでした。
(Python) トレースバックの例 (traceback)
ロガー
(Python) logging.getLogger(name=None)
(Python) logging.Logger.propagate 属性
(Python) class logging.Logger.setLevel(level)
『名前付きロガー』取得時のロギングレベルは NOTSET で、『ルートロガー』取得時のロギングレベルは WARNING である旨の説明がありました。
(Python) ロギングレベル (..., logging.NOTSET, logging.DEBUG, logging.INFO, ...)
(Python) class logging.Logger.addHandler(hdlr)
(Python) class logging.Logger.debug(msg, *args, **kwargs)
(Python) class logging.Logger.info(msg, *args, **kwargs)
ハンドラ
(Python) class logging.StreamHandler(stream=None)
(Python) class logging.FileHandler(filename, mode='a', encoding=None, delay=False, errors=None)
(Python) class logging.Handler.setLevel(level)
『ハンドラ』生成時のロギングレベルは NOTSET(すべてのメッセージが処理される)である旨の説明がありました。
(Python) class logging.Handler.setFormatter(fmt)
フォーマッタ
(Python) class logging.Formatter(fmt=None, datefmt=None, style='%', validate=True, *, defaults=None)
(Python) LogRecord 属性 %(asctime)s, %(levelname)s, %(levelno)s, %(message)s, %(name)s, ...
パスリブ
(Python) class pathlib.Path(*pathsegments)
以上です。