注意。 multiprocessing.Processのログ出力 -残念ながら、この質問には答えられません。
マルチプロセッシングを介して(Windows上で)子プロセスを作成しています。子プロセスのstdoutおよびstderr出力のallを、コンソールに表示するのではなく、ログファイルにリダイレクトしたいと思います。私が見た唯一の提案は、子プロセスがsys.stdoutをファイルに設定することです。ただし、Windowsでのstdoutリダイレクトの動作により、これはすべてのstdout出力を効果的にリダイレクトするわけではありません。
問題を説明するために、次のコードでWindows DLLを構築します
#include <iostream>
extern "C"
{
__declspec(dllexport) void writeToStdOut()
{
std::cout << "Writing to STDOUT from test DLL" << std::endl;
}
}
次に、次のようなpythonスクリプトを作成して実行します。このスクリプトは、このDLLをインポートし、関数を呼び出します。
from ctypes import *
import sys
print
print "Writing to STDOUT from python, before redirect"
print
sys.stdout = open("stdout_redirect_log.txt", "w")
print "Writing to STDOUT from python, after redirect"
testdll = CDLL("Release/stdout_test.dll")
testdll.writeToStdOut()
私と同じ動作を確認するには、DLLは、Pythonが使用するものとは異なるCランタイムに対してビルドする必要があります。私の場合、pythonはVisualStudio 2010でビルドされていますが、私のDLLはVS2005でビルドされています。
私が見る動作は、コンソールが次のように表示することです。
> stdout_test.py
Writing to STDOUT from python, before redirect
Writing to STDOUT from test DLL
ファイルstdout_redirect_log.txtには、次のものが含まれています。
Writing to STDOUT from python, after redirect
つまり、sys.stdoutを設定しても、DLLによって生成されたstdout出力をリダイレクトできませんでした。 Windowsでのstdoutリダイレクトの基盤となるAPIの性質を考えると、これは驚くべきことではありません。私は以前にネイティブ/ C++レベルでこの問題に遭遇しましたが、プロセス内からstdoutを確実にリダイレクトする方法を見つけたことはありません。それは外部で行われなければなりません。
これが実際に私が子プロセスを起動するまさにその理由です-それは私がそのパイプに外部で接続できるようにするためであり、したがって私がそのすべての出力を傍受していることを保証します。 pywin32を使用して手動でプロセスを起動することでこれを確実に行うことができますが、進行状況を取得するために、マルチプロセッシングの機能、特にマルチプロセッシングPipeオブジェクトを介して子プロセスと通信する機能を使用できるようにしたいと思います。更新。問題は、そのIPCファシリティとの両方にマルチプロセッシングを使用して、すべてのを確実にリダイレクトする方法があるかどうかです。子のstdoutおよびstderrがファイルに出力されます。
UPDATE:multiprocessing.Processsのソースコードを見ると、静的メンバー_Popenがあり、使用されているクラスをオーバーライドするために使用できるようです。プロセスを作成します。 None(デフォルト)に設定されている場合、multiprocessing.forking._Popenを使用しますが、次のようになります。
multiprocessing.Process._Popen = MyPopenClass
プロセスの作成を上書きできます。ただし、これはmultiprocessing.forking._Popenから導出できますが、内部のものを実装にコピーする必要があるように見えます。これは不安定で、将来性があまりないように聞こえます。それが唯一の選択肢である場合は、代わりにpywin32を使用してすべてを手動で行うためにおそらくふっくらと思います。
あなたが提案する解決策は良いものです:あなたがそれらのstdout/stderrファイルハンドルに明示的にアクセスできるようにあなたのプロセスを手動で作成してください。次に、サブプロセスと通信するソケットを作成し、そのソケットでmultiprocessing.connectionを使用できます(multiprocessing.Pipeは同じタイプの接続オブジェクトを作成するため、これによりすべて同じIPC =機能)。
これは2ファイルの例です。
master.py:
import multiprocessing.connection
import subprocess
import socket
import sys, os
## Listen for connection from remote process (and find free port number)
port = 10000
while True:
try:
l = multiprocessing.connection.Listener(('localhost', int(port)), authkey="secret")
break
except socket.error as ex:
if ex.errno != 98:
raise
port += 1 ## if errno==98, then port is not available.
proc = subprocess.Popen((sys.executable, "subproc.py", str(port)), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
## open connection for remote process
conn = l.accept()
conn.send([1, "asd", None])
print(proc.stdout.readline())
subproc.py:
import multiprocessing.connection
import subprocess
import sys, os, time
port = int(sys.argv[1])
conn = multiprocessing.connection.Client(('localhost', port), authkey="secret")
while True:
try:
obj = conn.recv()
print("received: %s\n" % str(obj))
sys.stdout.flush()
except EOFError: ## connection closed
break
サブプロセスから非ブロッキング読み取りを取得するには、 この質問 に対する最初の回答を確認することもできます。
コメントで述べたように、サブプロセスをファイルにリダイレクトするよりも良い選択肢はないと思います。
コンソールのstdin/out/errがWindowsで機能する方法は、プロセスが生まれたときの各プロセスで、 stdハンドル が定義されています。 SetStdHandle で変更できます。 Pythonのsys.stdout
を変更するときは、pythonが印刷する場所のみを変更し、他のDLLが印刷する場所は変更しません。 DLLのCRTの一部は、GetStdHandleを使用して印刷先を見つけています。必要に応じて、WindowsAPIのDLLまたはpythonスクリプトでpywin32を使用して任意のパイピングを実行できます。 subprocess の方が簡単だと思いますが。
私はベースから外れていて何かが足りないと思いますが、ここで価値があるのは、あなたの質問を読んだときに頭に浮かんだことです。
Stdoutとstderrのすべてをインターセプトできる場合(あなたの質問からその印象を受けました)、各プロセスの周りにそのキャプチャ機能を追加またはラップしてみませんか?次に、キューを介してキャプチャされたものを、すべての出力で必要なことを実行できるコンシューマーに送信しますか?
私の状況では、sys.stdout.write
をPySideQTextEditに書き込むように変更しました。 sys.stdout
から読み取ることができず、sys.stdout
を読み取り可能に変更する方法がわかりませんでした。 2本のパイプを作成しました。 1つはstdout用で、もう1つはstderr用です。別のプロセスで、sys.stdout
とsys.stderr
をマルチプロセッシングパイプの子接続にリダイレクトします。メインプロセスで、stdoutとstderrの親パイプを読み取り、パイプデータをsys.stdout
とsys.stderr
にリダイレクトする2つのスレッドを作成しました。
import sys
import contextlib
import threading
import multiprocessing as mp
import multiprocessing.queues
from queue import Empty
import time
class PipeProcess(mp.Process):
"""Process to pipe the output of the sub process and redirect it to this sys.stdout and sys.stderr.
Note:
The use_queue = True argument will pass data between processes using Queues instead of Pipes. Queues will
give you the full output and read all of the data from the Queue. A pipe is more efficient, but may not
redirect all of the output back to the main process.
"""
def __init__(self, group=None, target=None, name=None, args=Tuple(), kwargs={}, *_, daemon=None,
use_pipe=None, use_queue=None):
self.read_out_th = None
self.read_err_th = None
self.pipe_target = target
self.pipe_alive = mp.Event()
if use_pipe or (use_pipe is None and not use_queue): # Default
self.parent_stdout, self.child_stdout = mp.Pipe(False)
self.parent_stderr, self.child_stderr = mp.Pipe(False)
else:
self.parent_stdout = self.child_stdout = mp.Queue()
self.parent_stderr = self.child_stderr = mp.Queue()
args = (self.child_stdout, self.child_stderr, target) + Tuple(args)
target = self.run_pipe_out_target
super(PipeProcess, self).__init__(group=group, target=target, name=name, args=args, kwargs=kwargs,
daemon=daemon)
def start(self):
"""Start the multiprocess and reading thread."""
self.pipe_alive.set()
super(PipeProcess, self).start()
self.read_out_th = threading.Thread(target=self.read_pipe_out,
args=(self.pipe_alive, self.parent_stdout, sys.stdout))
self.read_err_th = threading.Thread(target=self.read_pipe_out,
args=(self.pipe_alive, self.parent_stderr, sys.stderr))
self.read_out_th.daemon = True
self.read_err_th.daemon = True
self.read_out_th.start()
self.read_err_th.start()
@classmethod
def run_pipe_out_target(cls, pipe_stdout, pipe_stderr, pipe_target, *args, **kwargs):
"""The real multiprocessing target to redirect stdout and stderr to a pipe or queue."""
sys.stdout.write = cls.redirect_write(pipe_stdout) # , sys.__stdout__) # Is redirected in main process
sys.stderr.write = cls.redirect_write(pipe_stderr) # , sys.__stderr__) # Is redirected in main process
pipe_target(*args, **kwargs)
@staticmethod
def redirect_write(child, out=None):
"""Create a function to write out a pipe and write out an additional out."""
if isinstance(child, mp.queues.Queue):
send = child.put
else:
send = child.send_bytes # No need to pickle with child_conn.send(data)
def write(data, *args):
try:
if isinstance(data, str):
data = data.encode('utf-8')
send(data)
if out is not None:
out.write(data)
except:
pass
return write
@classmethod
def read_pipe_out(cls, pipe_alive, pipe_out, out):
if isinstance(pipe_out, mp.queues.Queue):
# Queue has better functionality to get all of the data
def recv():
return pipe_out.get(timeout=0.5)
def is_alive():
return pipe_alive.is_set() or pipe_out.qsize() > 0
else:
# Pipe is more efficient
recv = pipe_out.recv_bytes # No need to unpickle with data = pipe_out.recv()
is_alive = pipe_alive.is_set
# Loop through reading and redirecting data
while is_alive():
try:
data = recv()
if isinstance(data, bytes):
data = data.decode('utf-8')
out.write(data)
except EOFError:
break
except Empty:
pass
except:
pass
def join(self, *args):
# Wait for process to finish (unless a timeout was given)
super(PipeProcess, self).join(*args)
# Trigger to stop the threads
self.pipe_alive.clear()
# Pipe must close to prevent blocking and waiting on recv forever
if not isinstance(self.parent_stdout, mp.queues.Queue):
with contextlib.suppress():
self.parent_stdout.close()
with contextlib.suppress():
self.parent_stderr.close()
# Close the pipes and threads
with contextlib.suppress():
self.read_out_th.join()
with contextlib.suppress():
self.read_err_th.join()
def run_long_print():
for i in range(1000):
print(i)
print(i, file=sys.stderr)
print('finished')
if __name__ == '__main__':
# Example test write (My case was a QTextEdit)
out = open('stdout.log', 'w')
err = open('stderr.log', 'w')
# Overwrite the write function and not the actual stdout object to prove this works
sys.stdout.write = out.write
sys.stderr.write = err.write
# Create a process that uses pipes to read multiprocess output back into sys.stdout.write
proc = PipeProcess(target=run_long_print, use_queue=True) # If use_pipe=True Pipe may not write out all values
# proc.daemon = True # If daemon and use_queue Not all output may be redirected to stdout
proc.start()
# time.sleep(5) # Not needed unless use_pipe or daemon and all of stdout/stderr is desired
# Close the process
proc.join() # For some odd reason this blocks forever when use_queue=False
# Close the output files for this test
out.close()
err.close()