ドキュメントに書いてあっても 読んでない or 忘れていた 私のためのメモ。
Pythonで 子プロセスを実行するときには subprocess モジュールを使うことが推奨されています。
その子プロセスの標準出力やエラー出力を受け取るときに、 p.stdout.read()
や p.stderr.read()
を使っていると
子プロセスの出力するデータ量が多い時に 刺さって しまいます。代わりに p.communicate()
を使うと良いです。
その件については、下記ドキュメントの中段辺りに
http://docs.python.jp/2.7/library/subprocess.html
警告
stdin.write(), stdout.read(), stderr.read() を利用すると、別のパイプのOSパイプバッファがいっぱいになってデッドロックする恐れがあります。これを避けるためには communicate() を利用してください。
と書かれていますが、うっかり使って(そしてその問題にハマって)いたのでメモです。
以下、簡単な検証コードです。
read()
は子プロセスの出力データ量が10KBのときはOKでしたが、100KBのときはダメでした。
communicate()
を使えば両方ともOKでした。
import subprocess
def bad_impl(cmd):
print "start bad_impl %s" % cmd
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print "waiting"
p.wait()
stdout_data = p.stdout.read()
stderr_data = p.stderr.read()
print "finish: %d %d" % (len(stdout_data), len(stderr_data))
return p.returncode, stdout_data, stderr_data
def better_impl(cmd):
print "start better_impl %s" % cmd
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print "waiting"
stdout_data, stderr_data = p.communicate()
print "finish: %d %d" % (len(stdout_data), len(stderr_data))
return p.returncode, stdout_data, stderr_data
command1 = "python -c 'print str(1) * 10000'" # 10KB の出力
command2 = "python -c 'print str(1) * 100000'" # 100KBの出力
better_impl(command1)
print "=" * 50
bad_impl(command1)
print "=" * 50
better_impl(command2)
print "=" * 50
bad_impl(command2) # これが失敗する
% python spike.py
start better_impl python -c 'print str(1) * 10000'
waiting
finish: 10001 0
==================================================
start bad_impl python -c 'print str(1) * 10000'
waiting
finish: 10001 0
==================================================
start better_impl python -c 'print str(1) * 100000'
waiting
finish: 100001 0
==================================================
start bad_impl python -c 'print str(1) * 100000'
waiting
↑ここで制御が戻ってこない
更に言えば
communicate()
の部分にも以下のような記述があります。
ノート
受信したデータはメモリ中にバッファされます。そのため、返されるデータが大きいかあるいは制限がないような場合はこのメソッドを使うべきではありません。
利用できるメモリ上限を超える場合はこれもダメというわけですね。
その場合は逐次読みだして、ファイルに保存するなりしないといけないわけですね。
そういう数GBのデータを扱うような場合は、その時にまた考えよう… (^^;