我想从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 …