私はPythonでサブプロセスパイプラインをテストしています。以下のプログラムがpythonで直接実行できることは承知していますが、それは重要ではありません。パイプラインをテストしたいので、その使用方法を知っています。
私のシステムはLinuxUbuntu 9.04で、デフォルトはpython 2.6です。
私はこれから始めました ドキュメントの例 。
_from subprocess import Popen, PIPE
p1 = Popen(["grep", "-v", "not"], stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]
print output
_
それは機能しますが、_p1
_のstdin
がリダイレクトされていないため、パイプにフィードするためにターミナルに入力する必要があります。 stdinを閉じる_^D
_と入力すると、必要な出力が得られます。
ただし、python文字列変数を使用してパイプにデータを送信したい。最初にstdinに書き込んでみた:
_p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
p1.stdin.write('test\n')
output = p2.communicate()[0] # blocks forever here
_
動作しませんでした。最後の行で代わりにp2.stdout.read()
を使用しようとしましたが、ブロックされます。 p1.stdin.flush()
とp1.stdin.close()
を追加しましたが、どちらも機能しませんでした。それから私はコミュニケーションに移りました:
_p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
p1.communicate('test\n') # blocks forever here
output = p2.communicate()[0]
_
だから、それはまだそれではありません。
単一のプロセスの実行(上記の_p1
_のように、_p2
_の削除)が完全に機能することに気付きました。また、ファイルハンドルを_p1
_(stdin=open(...)
)に渡すこともできます。したがって、問題は次のとおりです。
ブロックせずに、Pythonで2つ以上のサブプロセスのパイプラインにデータを渡すことは可能ですか?何故なの?
シェルを実行してパイプラインをシェルで実行できることは承知していますが、それは私が望んでいることではありません。
UPDATE 1:以下のAaron Digullaのヒントに従って、スレッドを使用して動作させようとしています。
まず、スレッドでp1.communicateを実行してみました。
_p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
t = threading.Thread(target=p1.communicate, args=('some data\n',))
t.start()
output = p2.communicate()[0] # blocks forever here
_
さて、動作しませんでした。 .write()
やp2.read()
に変更するなどの他の組み合わせを試しました。何もありません。次に、反対のアプローチを試してみましょう。
_def get_output(subp):
output = subp.communicate()[0] # blocks on thread
print 'GOT:', output
p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
t = threading.Thread(target=get_output, args=(p2,))
t.start()
p1.communicate('data\n') # blocks here.
t.join()
_
コードはどこかでブロックされてしまいます。スポーンされたスレッド、メインスレッド、またはその両方。だからそれはうまくいきませんでした。それを機能させる方法を知っているなら、機能するコードを提供できればもっと簡単になるでしょう。私はここで試しています。
UPDATE 2
Paul Du Boisがいくつかの情報で以下に答えたので、私はさらにテストを行いました。 _subprocess.py
_モジュール全体を読み、それがどのように機能するかを理解しました。だから私はそれをコードに正確に適用してみました。
私はLinuxを使用していますが、スレッドを使用してテストしていたため、最初のアプローチは、_subprocess.py
_のcommunicate()
メソッドに表示される正確なWindowsスレッドコードを複製することでしたが、1つではなく2つのプロセスでした。これが私が試したものの全体のリストです:
_import os
from subprocess import Popen, PIPE
import threading
def get_output(fobj, buffer):
while True:
chunk = fobj.read() # BLOCKS HERE
if not chunk:
break
buffer.append(chunk)
p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
b = [] # create a buffer
t = threading.Thread(target=get_output, args=(p2.stdout, b))
t.start() # start reading thread
for x in xrange(100000):
p1.stdin.write('hello world\n') # write data
p1.stdin.flush()
p1.stdin.close() # close input...
t.join()
_
上手。それはうまくいきませんでした。 p1.stdin.close()
が呼び出された後でも、p2.stdout.read()
はブロックされます。
次に、_subprocess.py
_でposixコードを試しました。
_import os
from subprocess import Popen, PIPE
import select
p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
numwrites = 100000
to_read = [p2.stdout]
to_write = [p1.stdin]
b = [] # create buffer
while to_read or to_write:
read_now, write_now, xlist = select.select(to_read, to_write, [])
if read_now:
data = os.read(p2.stdout.fileno(), 1024)
if not data:
p2.stdout.close()
to_read = []
else:
b.append(data)
if write_now:
if numwrites > 0:
numwrites -= 1
p1.stdin.write('hello world!\n'); p1.stdin.flush()
else:
p1.stdin.close()
to_write = []
print b
_
また、select.select()
をブロックします。 print
sを広めることで、私はこれを見つけました:
p1.stdin
_に書き込まれます。numwrites
の終わりに、p1.stdin.close()
が呼び出されます。select()
がブロックを開始すると、_to_read
_だけが何か_p2.stdout
_を持ちます。 _to_write
_はすでに空です。os.read()
callは常に何かを返すため、p2.stdout.close()
が呼び出されることはありません。両方のテストからの結論:パイプラインの最初のプロセス(例ではstdin
)のgrep
を閉じるとバッファリングされた出力を次の出力にダンプして死ぬようにしないでください。
それを機能させる方法はありませんか?
PS:一時ファイルは使いたくありません。すでにファイルでテストしていて、機能することはわかっています。そして、私はウィンドウズを使いたくありません。
私はそれを行う方法を見つけました。
それはスレッドについてではなく、select()についてでもありません。
最初のプロセス(grep
)を実行すると、パイプごとに1つずつ、2つの低レベルファイル記述子が作成されます。それらをa
とb
と呼びましょう。
2番目のプロセスを実行すると、b
がcut
sdtin
に渡されます。しかし、Popen
--_close_fds=False
_には脳死のデフォルトがあります。
その効果は、cut
もa
を継承することです。したがって、grep
のプロセスでstdinがまだ開いているため(a
は無視します)、cut
を閉じてもcut
は終了できません。
次のコードは完全に実行されます。
_from subprocess import Popen, PIPE
p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE, close_fds=True)
p1.stdin.write('Hello World\n')
p1.stdin.close()
result = p2.stdout.read()
assert result == "Hello Worl\n"
_
_close_fds=True
_はデフォルトである必要があります UNIXシステムでは。 Windowsでは、閉じますall fdsなので、配管ができなくなります。
編集:
PS:この回答を読んで同様の問題を抱えている人のために:pooryorickがコメントで言ったように、_p1.stdin
_に書き込まれたデータがバッファーよりも大きい場合もブロックされる可能性があります。その場合、データをより小さな部分にチャンクし、select.select()
を使用していつ読み取り/書き込みを行うかを知る必要があります。問題のコードは、それを実装する方法についてのヒントを与えるはずです。
EDIT2:pooryorickの助けを借りて、別の解決策を見つけました-_close_fds=True
_を使用して閉じる代わりに[〜#〜] all [〜#〜] fds、fd
sは、2番目のプロセスを実行するときに、最初のプロセスに属し、機能します。クロージングは子で行う必要があるため、Popenの_preexec_fn
_関数はそれを行うのに非常に便利です。 p2を実行すると、次のことができます。
_p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE, stderr=devnull, preexec_fn=p1.stdin.close)
_
Pythonで大きなファイルを操作する場合は、2つの原則を一律に適用する必要があります。
EOF
を待たないようにする必要があります。別の方法は、ノンブロッキングIOを使用することですが、これは標準のPythonでは面倒です。非ブロッキングプリミティブを使用して同期IO APIを実装する軽量スレッドライブラリについては、 gevent を参照してください。
大まかに言って愚かなパイプラインを構築します
{cat /usr/share/dict/words} | grep -v not \
| {upcase, filtered tee to stderr} | cut -c 1-10 \
| {translate 'E' to '3'} | grep K | grep Z | {downcase}
中括弧{}
の各ステージはPythonで実装され、他のステージは標準の外部プログラムを使用します。TL;DR:参照この要点 。
予想される輸入から始めます。
#!/usr/bin/env python
from subprocess import Popen, PIPE
import sys, threading
パイプラインの最後のPython実装ステージを除くすべてがスレッドに入る必要があるため、IOは他のステージをブロックしません。代わりに、これらはPython =サブプロセスを実際に並行して実行したい場合(GILは避けてください)。
def writer(output):
for line in open('/usr/share/dict/words'):
output.write(line)
output.close()
def filter(input, output):
for line in input:
if 'k' in line and 'z' in line: # Selective 'tee'
sys.stderr.write('### ' + line)
output.write(line.upper())
output.close()
def leeter(input, output):
for line in input:
output.write(line.replace('E', '3'))
output.close()
これらはそれぞれ独自のスレッドに配置する必要があります。これは、この便利な関数を使用して行います。
def spawn(func, **kwargs):
t = threading.Thread(target=func, kwargs=kwargs)
t.start()
return t
Popen
とPython spawn
を使用したステージを使用して外部ステージを作成します。引数bufsize=-1
は、システムのデフォルトのバッファリング(通常は4 kiB)を使用するように指示します。これは通常、デフォルト(バッファなし)またはラインバッファリングよりも高速ですが、遅延なしで出力を視覚的に監視する場合は、ラインバッファリングが必要になります。
grepv = Popen(['grep','-v','not'], stdin=PIPE, stdout=PIPE, bufsize=-1)
cut = Popen(['cut','-c','1-10'], stdin=PIPE, stdout=PIPE, bufsize=-1)
grepk = Popen(['grep', 'K'], stdin=PIPE, stdout=PIPE, bufsize=-1)
grepz = Popen(['grep', 'Z'], stdin=grepk.stdout, stdout=PIPE, bufsize=-1)
twriter = spawn(writer, output=grepv.stdin)
tfilter = spawn(filter, input=grepv.stdout, output=cut.stdin)
tleeter = spawn(leeter, input=cut.stdout, output=grepk.stdin)
上記のように組み立てると、パイプライン内のすべてのバッファーがいっぱいになりますが、最後から読み取っている人がいないため(grepz.stdout
)、すべてブロックされます。 grepz.stdout.read()
を1回呼び出すだけですべてを読み取ることができますが、大きなファイルには大量のメモリが使用されます。代わりに、増分を読み取ります。
for line in grepz.stdout:
sys.stdout.write(line.lower())
スレッドとプロセスは、EOF
に達するとクリーンアップされます。を使用して明示的にクリーンアップできます
for t in [twriter, tfilter, tleeter]: t.join()
for p in [grepv, cut, grepk, grepz]: p.wait()
内部的には、subprocess.Popen
はfork
を呼び出し、パイプファイル記述子を構成し、exec
を呼び出します。 fork
の子プロセスには、親プロセス内のすべてのファイル記述子のコピーがあり、対応するリーダーがEOF
。これは、パイプを手動で閉じるか(close_fds=True
または適切なpreexec_fn
引数をsubprocess.Popen
に設定することにより)、または FD_CLOEXEC
フラグを設定してexec
がファイル記述子を自動的に閉じることで修正できます。このフラグはPython-2.7以降で自動的に設定されます。 issue12786 を参照してください。以前のバージョンのPythonを呼び出すことで、Python-2.7の動作を取得できます。
p._set_cloexec_flags(p.stdin)
p.stdin
を引数として後続のsubprocess.Popen
に渡す前。
パイプを期待どおりに機能させるための3つの主なトリックがあります
パイプの両端が異なるスレッド/プロセスで使用されていることを確認してください(上部にある例のいくつかはこの問題に悩まされています)。
各プロセスでパイプの未使用の端を明示的に閉じます
バッファリングを無効にする(Python -uオプション)か、ptyを使用するか、データに影響を与えないもの(おそらく '\ n'ですが、適切なもの)でバッファを埋めることによってバッファリングを処理します。
Python "pipeline"モジュール(私は作成者です)の例は、シナリオに正確に適合し、低レベルの手順をかなり明確にします。
http://pypi.python.org/pypi/pipeline/
最近では、プロデューサー-プロセッサー-コンシューマー-コントローラーのパターンの一部としてサブプロセスモジュールを使用しました。
http://www.darkarchive.org/w/Pub/PythonInteract
この例では、ptyを使用せずにバッファリングされた標準入力を扱い、どのパイプの端をどこで閉じる必要があるかも示しています。私はスレッド化よりもプロセスを好みますが、原則は同じです。さらに、プロデューサーにフィードし、コンシューマーから出力を収集するキューの同期と、それらをクリーンにシャットダウンする方法を示します(キューに挿入されたセンチネルに注意してください)。このパターンにより、最近の出力に基づいて新しい入力を生成できるため、再帰的な検出と処理が可能になります。
Noskloが提供するソリューションは、パイプの受信側に書き込まれるデータが多すぎるとすぐに壊れます。
from subprocess import Popen, PIPE
p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE, close_fds=True)
p1.stdin.write('Hello World\n' * 20000)
p1.stdin.close()
result = p2.stdout.read()
assert result == "Hello Worl\n"
このスクリプトがマシンでハングしない場合は、「20000」をオペレーティングシステムのパイプバッファのサイズを超える値に増やしてください。
これは、オペレーティングシステムが入力を「grep」にバッファリングしているためですが、そのバッファがいっぱいになると、p1.stdin.write
から何かが読み取られるまでp2.stdout
呼び出しがブロックされます。おもちゃのシナリオでは、同じプロセスでパイプへの書き込み/パイプからの読み取りを行うことができますが、通常の使用法では、1つのスレッド/プロセスから書き込み、別のスレッド/プロセスから読み取る必要があります。これは、subprocess.popen、os.pipe、os.popen *などに当てはまります。
もう1つの工夫は、同じパイプの以前の出力から生成されたアイテムをパイプに供給し続けたい場合があることです。解決策は、パイプフィーダーとパイプリーダーの両方をmanプログラムと非同期にし、2つのキューを実装することです。1つはメインプログラムとパイプフィーダーの間、もう1つはメインプログラムとパイプリーダーの間です。 PythonInteract はその一例です。
サブプロセスは便利なモデルですが、os.popenとos.forkの呼び出しの詳細が内部に隠されているため、使用する下位レベルの呼び出しよりも処理が難しい場合があります。このため、サブプロセスは、プロセス間パイプが実際にどのように機能するかを知るための良い方法ではありません。
これはいくつかのスレッドで行う必要があります。そうしないと、データを送信できない状況になります。p2の出力を読み取らないためにp2がp1の出力を読み取らないため、子p1は入力を読み取れません。
したがって、p2が書き出すものを読み取るバックグラウンドスレッドが必要です。これにより、パイプにデータを書き込んだ後もp2を続行できるため、p1から次の入力行を読み取ることができます。これにより、p1は送信されたデータを処理できます。
または、バックグラウンドスレッドを使用してデータをp1に送信し、メインスレッドでp2からの出力を読み取ることもできます。ただし、どちらの側もスレッドである必要があります。
close_fds=True
なしでは実行できないというnoskloの主張(この質問に対する他のコメントを参照)への応答:
close_fds=True
は、他のファイル記述子を開いたままにしている場合にのみ必要です。複数の子プロセスを開くときは、継承される可能性のある開いているファイルを追跡し、不要なファイルを明示的に閉じることをお勧めします。
from subprocess import Popen, PIPE
p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p1.stdin.write('Hello World\n')
p1.stdin.close()
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
result = p2.stdout.read()
assert result == "Hello Worl\n"
close_fds
のデフォルトはFalse
です。これは、サブプロセスが呼び出し側プログラムを信頼して、開いているファイル記述子で何を行っているかを認識し、呼び出し元に、必要に応じてすべてを閉じる簡単なオプションを提供するためです。行う。
しかし、本当の問題は、おもちゃの例を除いて、パイプバッファがあなたを噛むことです。この質問に対する他の回答で述べたように、経験則では、リーダーとライターを同じプロセス/スレッドで開かないようにします。双方向通信にサブプロセスモジュールを使用したい人は、最初にos.pipeとos.forkを研究するのに役立ちます。 良い例 を見ることができれば、実際にはそれほど難しくはありません。
あなたは間違った問題を調べているのではないかと思います。確かに、アーロンが言うように、パイプラインの最初のプロデューサーとパイプラインの最後のコンシューマーの両方になろうとすると、デッドロック状態に陥りやすくなります。これがcommunicate()が解決する問題です。
stdinとstdoutは異なるサブプロセスオブジェクト上にあるため、communicate()は正確には正しくありません。しかし、subprocess.pyの実装を見ると、Aaronが提案したとおりに動作していることがわかります。
読み取りと書き込みの両方を通信することがわかると、2回目の試行で、communicate()がp1の出力を求めてp2と競合することがわかります。
p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
# ...
p1.communicate('data\n') # reads from p1.stdout, as does p2
私はwin32で実行しています。これは、明らかに異なるI/Oとバッファリングの特性を持っていますが、これは私にとってはうまくいきます。
p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
t = threading.Thread(target=get_output, args=(p2,))
t.start()
p1.stdin.write('hello world\n' * 100000)
p1.stdin.close()
t.join()
ナイーブなスレッド化されていないp2.read()を使用すると、デッドロックが発生するように入力サイズを調整しました。
また、ファイルにバッファリングしてみることもできます。
fd, _ = tempfile.mkstemp()
os.write(fd, 'hello world\r\n' * 100000)
os.lseek(fd, 0, os.SEEK_SET)
p1 = Popen(["grep", "-v", "not"], stdin=fd, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
print p2.stdout.read()
これは、デッドロックなしでも機能します。
上記のコメントの1つで、私はnoskloに、select.select
に関する彼の主張を裏付けるコードを投稿するか、以前に反対票を投じた私の回答に賛成するように要求しました。彼は次のコードで応答しました。
from subprocess import Popen, PIPE
import select
p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE, close_fds=True)
data_to_write = 100000 * 'hello world\n'
to_read = [p2.stdout]
to_write = [p1.stdin]
b = [] # create buffer
written = 0
while to_read or to_write:
read_now, write_now, xlist = select.select(to_read, to_write, [])
if read_now:
data = p2.stdout.read(1024)
if not data:
p2.stdout.close()
to_read = []
else:
b.append(data)
if write_now:
if written < len(data_to_write):
part = data_to_write[written:written+1024]
written += len(part)
p1.stdin.write(part); p1.stdin.flush()
else:
p1.stdin.close()
to_write = []
print b
このスクリプトの問題の1つは、システムパイプバッファのサイズ/性質を2番目に推測することです。スクリプトが1024のようなマジックナンバーを削除できれば、失敗は少なくなります。
大きな問題は、このスクリプトコードが、データ入力と外部プログラムの正しい組み合わせでのみ一貫して機能することです。 grepとcutはどちらも行で機能するため、内部バッファーの動作は少し異なります。 「cat」のようなより一般的なコマンドを使用し、パイプに小さなデータを書き込むと、致命的な競合状態がより頻繁に発生します。
from subprocess import Popen, PIPE
import select
import time
p1 = Popen(["cat"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cat"], stdin=p1.stdout, stdout=PIPE, close_fds=True)
data_to_write = 'hello world\n'
to_read = [p2.stdout]
to_write = [p1.stdin]
b = [] # create buffer
written = 0
while to_read or to_write:
time.sleep(1)
read_now, write_now, xlist = select.select(to_read, to_write, [])
if read_now:
print 'I am reading now!'
data = p2.stdout.read(1024)
if not data:
p1.stdout.close()
to_read = []
else:
b.append(data)
if write_now:
print 'I am writing now!'
if written < len(data_to_write):
part = data_to_write[written:written+1024]
written += len(part)
p1.stdin.write(part); p1.stdin.flush()
else:
print 'closing file'
p1.stdin.close()
to_write = []
print b
この場合、2つの異なる結果が現れます。
write, write, close file, read -> success
write, read -> hang
繰り返しになりますが、私はnoskloに、select.select
を使用して単一のスレッドからの任意の入力とパイプのバッファリングを処理することを示すコードを投稿するか、応答に賛成するように要求します。
結論:単一のスレッドからパイプの両端を操作しようとしないでください。それだけの価値はありません。これを正しく行う方法の優れた低レベルの例については、 pipeline を参照してください。
SpooledTemporaryFileの使用はどうですか?これは問題を回避します(しかしおそらく解決しません):
http://docs.python.org/library/tempfile.html#tempfile.SpooledTemporaryFile
ファイルのように書き込むこともできますが、実際にはメモリブロックです。
それとも私は完全に誤解していますか...