ようへいの日々精進XP

よかろうもん

Python スクリプトの標準出力をパイプでつなぐ時に気をつけたいこと

これは

qiita.com

初老丸 Advent Calendar 2017 9 日目の記事になる予定です.

tl;dr

Python スクリプト内で以下のように Print() を利用して 'foo' を標準出力させつつ, tee コマンドを使ってファイルにも書き出した際に学んだことのメモ.

import time

while True:
    print('foo')
    time.sleep(3)

あれっ??

以下のように実行してファイルにも 'foo' を書き込みたかったんですが...

$ cat foo.py
import time

while True:
    print('foo')
    time.sleep(3)
    
$ python foo.py | tee -a foo.txt


... 何も出力されないし...

これは困った, 困った, こまどり姉妹

Python のヘルプを見ると...

$ python --version
Python 2.7.13
$ python --help
usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
-B     : don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x

... 略 ...

-u     : unbuffered binary stdout and stderr; also PYTHONUNBUFFERED=x
         see man page for details on internal buffering relating to '-u'

... 略 ...

PYTHONHASHSEED: if this variable is set to 'random', the effect is the same
   as specifying the -R option: a random value is used to seed the hashes of
   str, bytes and datetime objects.  It can also be set to an integer
   in the range [0,4294967295] to get hash values with a predictable seed.

ふむふむ...man してみると以下のように出力されました.

$ man python2.7
... 略 ...

       -u     Force  stdin,  stdout  and  stderr  to  be totally unbuffered.  On systems where it matters, also put stdin, stdout and stderr in binary mode.  Note that there is internal
              buffering in xreadlines(), readlines() and file-object iterators ("for line in sys.stdin") which is not influenced by this option.  To work around this, you will  want  to
              use "sys.stdin.readline()" inside a "while 1:" loop.

... 略 ...

-u オプションは stdin, stdout, stderr バッファリングさせない為に利用するオプションということが解りました. デフォルトで Python は標準出力が改行されるタイミングでバッファがフラッシュされ, パイプで別のプロセスで処理させる場合にはバッファリングするとのこと.(参考)

以下のように -u オプションを付与して実行するだけで, バッファリングが無効になり, 冒頭のスクリプトと tee コマンドを併用して標準出力とファイル出力を一緒に行うことが出来ました.

$ $ python -u foo.py | tee -a foo.log
foo
foo
foo

One more thing

-u オプションを付与する以外にも, sys モジュールの sys.stdout.flush() を利用してバッファをフラッシュすることで同様に出力を得ることが出来ました.

import time
import sys

while True:
    print('foo')
    sys.stdout.flush()
    time.sleep(3)

以下のように出力されました.

$ cat foo2.py
import time
import sys

while True:
    print('foo')
    sys.stdout.flush()
    time.sleep(3)
 
$ python foo2.py | tee -a foo2.log
foo
foo
foo
^CTraceback (most recent call last):
  File "foo2.py", line 7, in <module>
    time.sleep(3)
KeyboardInterrupt

$ cat foo2.log
foo
foo
foo

以上

メモでした.