Python の標準出力には3つのバッファリングモードがあります。
- **バッファなし。**バイナリモードとなり、データが即座に渡されます1。
- **行バッファリング。**改行されるたびに、暗黙的にフラッシュします2。
- **ブロックバッファリング。**バッファが足りなくなるたびに書き出されます3。
デフォルトでは、インタラクティブだと行バッファリングとなり、それ以外だとブロックバッファリングとなります4。この記事では、ブロックバッファリングとなっている標準出力を、行バッファリングあるいはバッファリングなしに設定する方法を4つ紹介します。
python -u
python
コマンドに -u
引数を与えると、標準入出力及び標準エラー出力がすべてバッファリングなしになります1。ブロックバッファリングを止める最も簡単な方法です。一時的にバッファリングを止めた状態でスクリプトを実行したい場合に適しています。しかし、実行する度に設定する必要があるため、頻繁に使う場合はあまり良い選択肢ではありません。
PYTHONUNBUFFERED
環境変数
PYTHONUNBUFFERED
環境変数に空でない文字列を設定すると、-u
オプションを指定したときと同様の効果を得られます5。特定のマシンで起動される Python をすべてバッファリングなしに設定したい場合や、-u
引数を一々設定するのが面倒な場合に便利です。
sys.stdout
を置き換える
sys.stdout
にバッファリングモードを変更したファイルオブジェクトを格納することで、ブロックバッファリングを止める方法です。
行バッファリングに設定する場合
import io
import sys
def line_buffered_text_io(stream):
if (not isinstance(stream, io.TextIOWrapper)
or not hasattr(stream, 'buffer')):
return stream
return io.TextIOWrapper(stream.buffer, encoding=stream.encoding,
errors=stream.errors, line_buffering=True)
sys.stdout = line_buffered_text_io(sys.stdout)
sys.stderr = line_buffered_text_io(sys.stderr)
sys.stdout
を line_buffering
を有効にした io.TextIOWrapper
で置き換えています。sys.stderr
も同様です。
バッファリングなしに設定する場合
import io
import sys
def unbuffered_text_io(stream):
if (not isinstance(stream, io.TextIOWrapper)
or not hasattr(stream, 'buffer')):
return stream
raw = stream.buffer
if isinstance(raw, io.BufferedIOBase) and hasattr(raw, 'raw'):
raw = raw.raw
return io.TextIOWrapper(raw, encoding=stream.encoding,
errors=stream.errors, write_through=True)
sys.stdout = unbuffered_text_io(sys.stdout)
sys.stderr = unbuffered_text_io(sys.stderr)
sys.stdout
を write_through
を有効にした io.TextIOWrapper
で置き換えています。ただし、それだけだと下層の io.BufferedWriter
によってバッファリングされてしまうので、更に下層にある io.FileIO
を取り出して対処しています。
なお、write_through
コンストラクタ引数は Python 3.3 以降でないと利用できないため6、それ未満のバージョンで使用する場合は write_through=True
を削除してください。
問題点
ライブラリによって置き換え前の sys.stdout
が保持されてしまう
ライブラリがインポート時に sys.stdout
を保持してしまった場合、後で sys.stdout
を置き換えても意味がありません。そのため、ライブラリをインポートする前に置き換えを完了する必要があります。
'isort:skip_file'
import io
import sys
def unbuffered_text_io(stream):
if (not isinstance(stream, io.TextIOWrapper)
or not hasattr(stream, 'buffer')):
return stream
raw = stream.buffer
if isinstance(raw, io.BufferedIOBase) and hasattr(raw, 'raw'):
raw = raw.raw
return io.TextIOWrapper(raw, encoding=stream.encoding,
errors=stream.errors, write_through=True)
sys.stdout = unbuffered_text_io(sys.stdout)
sys.stderr = unbuffered_text_io(sys.stderr)
# sys.stdout を保持するライブラリのインポート
import bottle # noqa
ただインポートを遅らせただけだと Linter に怒られたり、インポートの自動ソートが行われるため、noqa や isort:skip_file を追記する必要があります。
余談ですが、私は Bottle が sys.stdout.write
を保持していることに気づかず1時間以上溶かしました(盲点でした...)。皆さんは同じ轍を踏まないように気をつけてください。
sys.__stdout__
ちなみに、置き換え前のファイルオブジェクトは sys.__stdout__
でいつでも参照できます。そのため、もしライブラリが sys.__stdout__
を参照している場合は、sys.stdout
を置き換えても意味がありません(そんなライブラリがあるかどうかは分かりません)。
sys.stdout.reconfigure
Python 3.7 以降では io.TextIOWrapper
に reconfigure
というメソッドが追加されています7。これを使えば、既存のオブジェクトを置き換えずにバッファリングモードを変更することができます。
import sys
sys.stdout.reconfigure(line_buffering=True)
sys.stderr.reconfigure(line_buffering=True)
これで行バッファリングになります。インポート順序等を考えずに済むのでラクチンです。
ちなみに、reconfigure
には write_through
引数がありますが、有効にしても下層の io.BufferedWriter
によってバッファリングされてしまうので意味がありません。
〆
4つの中から状況にあった方法を選んで、バッファリングモードを変更すると良いでしょう。
-
io.TextIOWrapper の
line_buffering
コンストラクタ引数を参照。 ↩