subprocess,从STDOUT(Windows)读取时重复写入STDIN

我想从python调用一个外部进程。 我正在调用的过程读取一个inputstring,并给出标记化的结果,并等待另一个input(如果有帮助,二进制是MeCab标记器)。

我需要通过调用这个过程来标记数千行string。

问题是Popen.communicate()工作,但在发出STDOUT结果之前等待进程死亡。 我不想继续closures并打开数千次新的子stream程。 (而且我不想把整个文本发送出去,将来可能会轻易地增长到数万行。)

from subprocess import PIPE, Popen with Popen("mecab -O wakati".split(), stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=False, universal_newlines=True, bufsize=1) as proc: output, errors = proc.communicate("foobarbaz") print(output) 

我试过读取proc.stdout.read()而不是使用通信,但是它被stdin阻塞,并且不会在proc.stdin.close()之前返回任何结果。 这又意味着我需要每次创build一个新的进程。

我试图从下面类似的问题实现队列和线程,但它要么不返回任何东西,所以它被卡在While True ,或者当我通过重复发送string强制stdin缓冲区填充,它会输出所有的结果在一旦。

 from subprocess import PIPE, Popen from threading import Thread from queue import Queue, Empty def enqueue_output(out, queue): for line in iter(out.readline, b''): queue.put(line) out.close() p = Popen('mecab -O wakati'.split(), stdout=PIPE, stdin=PIPE, universal_newlines=True, bufsize=1, close_fds=False) q = Queue() t = Thread(target=enqueue_output, args=(p.stdout, q)) t.daemon = True t.start() p.stdin.write("foobarbaz") while True: try: line = q.get_nowait() except Empty: pass else: print(line) break 

也看了Pexpect的路线,但它的Windows端口不支持一些重要的模块(基于pty的),所以我也不能应用。

我知道有很多类似的答案,我已经尝试了大部分。 但我没有试过似乎在Windows上工作。

编辑:关于我使用的二进制文件,当我通过命令行使用它的一些信息。 它运行并标记我给的句子,直到我完成并强行closures程序。

(… waits_for_input – > input_recieved – > output – > waits_for_input …)

谢谢。

如果mecab使用具有默认缓冲的C FILE流,则管道输出具有4 KiB缓冲区。 这里的想法是,一个程序可以有效地使用任意大小的读取和写入缓冲区,底层的标准I / O实现处理自动填充和刷新更大的缓冲区。 这可以最大限度地减少所需的系统调用次数,并最大化吞吐量。 显然你不希望这种行为的交互式控制台或终端I / O或写入stderr 。 在这些情况下,C运行时使用行缓冲或无缓冲。

程序可以覆盖这种行为,有些程序可以通过命令行选项设置缓冲区大小。 例如,Python有“-u”(无缓冲)选项和PYTHONUNBUFFERED环境变量。 如果mecab没有类似的选项,那么在Windows上没有通用的解决方法。 C运行时的情况太复杂了。 Windows进程可以静态或动态链接到一个或多个CRT。 Linux上的情况不同,因为Linux进程通常将单个系统CRT(例如GNU libc.so.6)加载到全局符号表中,这允许LD_PRELOAD库配置C FILE流。 Linux stdbuf使用这个技巧,例如stdbuf -o0 mecab -O wakati


一个尝试的方法是调用CreateConsoleScreenBuffer并从msvcrt.open_osfhandle获取句柄的msvcrt.open_osfhandle 。 然后通过这个作为stdout而不是使用管道。 子进程会将此视为TTY,并使用行缓冲而不是完全缓冲。 然而,管理这是不平凡的。 这将涉及读取(即ReadConsoleOutputCharacter )滑动缓冲区(调用GetConsoleScreenBufferInfo跟踪光标位置),这是由另一个进程积极写入。 这种互动并不是我所需要甚至是经验的。 但是我已经非交互式地使用了一个控制台屏幕缓冲区,也就是在孩子退出后读取缓冲区。 这允许从直接写入控制台而不是stdout程序读取多达9,999行的输出,例如调用WriteConsole或打开“CON”或“CONOUT $”的程序。

这是Windows的解决方法。 这也应该适用于其他操作系统。 下载一个控制台模拟器像ConEmu( https://conemu.github.io/ )启动它而不是mecab作为你的子进程。

 p = Popen(['conemu'] , stdout=PIPE, stdin=PIPE, universal_newlines=True, bufsize=1, close_fds=False) 

然后发送以下作为第一个输入:

 mecab -O wakafi & exit 

您正在让模拟器为您处理文件输出问题; 当您手动与之交互时,通常会这样做。 我仍在研究这个问题。 但已经看起来很有希望

唯一的问题是conemu是一个GUI应用程序; 所以如果没有其他的方式来钩入它的输入和输出,可能需要调整和重建源(它是开源的)。 我没有找到任何其他的方法; 但是这应该工作。

我问过在这里以某种控制台模式运行的问题; 所以你可以检查一下这个线程。 作者Maximus在SO …