- 概要
- 標準出力や標準エラー出力をIOオブジェクトにリダイレクトする
- contextlib.redirect_stdout, contextlib.redirect_stderrの実装の確認
- ロガーへのリダイレクト用クラスの実装
- 参考文献
概要
PythonAPIが提供されているソフトウェアでバッチ処理を行っていますが、その処理過程の標準出力や標準エラー出力をロガー(logging.Logger
)にリダイレクトしたいということがありました。標準ライブラリの contextlib
を利用することでスマートに実装できたので紹介です。
標準出力や標準エラー出力をIOオブジェクトにリダイレクトする
標準出力や標準エラー出力をリダイレクトするには、contextlib の contextlib.redirect_stdout
および contextlib.redirect_stderr
が便利です。
以下のように任意のIOオブジェクトを引数に渡してwith文で使うことで、with文内での標準出力や標準エラー出力がIOオブジェクトにリダイレクトされます。
import contextlib with open("test.txt", mode="w") as f, contextlib.redirect_stdout(f): print("this is first print message") # 標準出力には出力されず、"test.txt"に出力される print("this is second print message") # 標準出力に出力される
以下では、リダイレクト先とする LoggerIO
クラスを実装し、write
メソッドをロガーへの出力としています。このクラスのインスタンスを contextlib.redirect_stdout
に渡すことで標準出力をロガーにリダイレクトすることができます。
import contextlib import logging class LoggerIO: def __init__(self, logger): self.logger = logger def write(self, msg): if msg.strip() != "": self.logger.info(msg) def flush(self): pass logger = logging.getLogger(__name__) logger.setLevel("INFO") logger.addHandler(logging.FileHandler("test.log")) with contextlib.redirect_stdout(LoggerIO(logger)): print("this is print message") # "test.log"に出力される
contextlib.redirect_stdout, contextlib.redirect_stderrの実装の確認
contextlibのソースコードを見ると、redirect_stdout
および redirect_stderr
は contextlib._RedirectStream
を継承しています。そして、クラス変数 _stream
の値によってリダイレクト元(標準出力もしくは標準エラー出力)を切り替えているようです。リダイレクト先の切り替えは、コンテキストマネージャを利用して sys.stdout
や sys.stderr
に代入されるIOオブジェクトを入れ替えることで実装されています。
class _RedirectStream(AbstractContextManager): _stream = None def __init__(self, new_target): self._new_target = new_target # We use a list of old targets to make this CM re-entrant self._old_targets = [] def __enter__(self): self._old_targets.append(getattr(sys, self._stream)) setattr(sys, self._stream, self._new_target) return self._new_target def __exit__(self, exctype, excinst, exctb): setattr(sys, self._stream, self._old_targets.pop()) class redirect_stdout(_RedirectStream): """Context manager for temporarily redirecting stdout to another file. # How to send help() to stderr with redirect_stdout(sys.stderr): help(dir) # How to write help() to a file with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow) """ _stream = "stdout" class redirect_stderr(_RedirectStream): """Context manager for temporarily redirecting stderr to another file.""" _stream = "stderr"
この contextlib._RedirectStream
を継承することでより柔軟な実装ができそうです。
ロガーへのリダイレクト用クラスの実装
以下では、contextlib._RedirectStream
を継承したクラスに直接 write
メソッドを実装しています。標準出力と標準エラー出力を引数で指定できたり、RedirectStreamToLogger
に直接ロガーを渡してリダイレクトできるようになるなど、よりスマートな実装になりました。
import contextlib import logging class RedirectStreamToLogger(contextlib._RedirectStream): def __init__(self, logger, stream="stdout"): super().__init__(new_target=self) self._stream = stream self.logger = logger def write(self, msg): if msg.strip() != "": self.logger.info(msg) def flush(self): pass logger = logging.getLogger(__name__) logger.setLevel("INFO") logger.addHandler(logging.FileHandler("test.log")) with RedirectStreamToLogger(logger, stream="stdout"): print("this is print message") # "test.log"に出力される