我想从python调用外部进程。我正在调用的进程读取一个输入字符串并给出标记化结果,并等待另一个输入(二进制文件是MeCab tokenizer,如果有帮助的话)。
我需要通过调用此过程来标记数千行字符串。
问题是Popen.communicate()有效但在发出STDOUT结果之前等待进程死亡。我不想继续关闭和打开新的子进程数千次。 (而且我不想发送整篇文章,将来很容易在成千上万行中增长。)
int main(){
int x;
}
我尝试过阅读proc.stdout.read()
而不是使用通信,但它被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.stdin.close()
之前不会返回任何结果。这也意味着我每次都需要创建一个新流程。
我试图从类似的问题实现队列和线程如下,但它要么没有返回任何东西,所以它被卡在stdin
上,或者当我通过重复发送字符串强制stdin缓冲区填充时,它一次输出所有结果。
While True
另外看了一下Pexpect路线,但它的windows端口不支持一些重要的模块(基于pty的模块),所以我也不能应用它。
我知道有很多相似的答案,我已经尝试了大部分答案。但我尝试过的任何东西似乎都不适用于Windows。
编辑:当我通过命令行使用它时,我正在使用的二进制文件的一些信息。它运行并标记我给出的句子,直到我完成并强行关闭程序。(... waits_for_input - > input_recieved - > output - > waits_for_input ...)
感谢。
答案 0 :(得分:3)
如果mecab使用带有默认缓冲的C FILE
流,则管道stdout具有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
获取句柄的文件描述符。然后将其传递为stdout
而不是使用管道。子进程将此视为TTY并使用行缓冲而不是完全缓冲。然而,管理这一点并非易事。它将涉及读取(即ReadConsoleOutputCharacter
)由另一个进程主动写入的滑动缓冲区(调用GetConsoleScreenBufferInfo
以跟踪光标位置)。这种互动并不是我曾经需要甚至尝试过的东西。但是我非交互式地使用了控制台屏幕缓冲区,即在孩子退出后读取缓冲区。这允许从直接写入控制台而不是stdout
的程序中读取最多9,999行输出,例如呼叫WriteConsole
或打开" CON"或" CONOUT $"。
答案 1 :(得分:0)
这是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应用程序;因此,如果没有其他方式来连接其输入和输出,可能必须从源(它的开源)调整和重建。我没有找到任何其他方式;但这应该有效。
我问过关于以某种控制台模式运行的问题here;所以你也可以检查那个线程。作者Maximus在SO ...
答案 2 :(得分:0)
代码
while True:
try:
line = q.get_nowait()
except Empty:
pass
else:
print(line)
break
本质上与
相同print(q.get())
效率较低,因为它在等待时会消耗CPU时间。显式循环不会使子流程中的数据更快到达;它到达时就到达。
对于处理不合作的二进制文件,我有一些建议,从最佳到最坏:
找到一个Python库并改用它。看来MeCab源代码树中有an official Python binding,我在PyPI上看到了一些预构建的软件包。您还可以查找可以用ctypes
或其他Python FFI调用的DLL构建。如果那不起作用...
找到在输出的每一行之后刷新的二进制文件。我在网上找到的最新Win32版本v0.98会在每行之后刷新。失败...
构建自己的二进制文件,每行之后刷新一次。找到主循环并在其中插入刷新调用应该足够容易。但是MeCab seems to explicitly flush already和git blame表示flush语句最后一次更改是在2011年,因此令您感到惊讶的是我曾经感到这个问题,并且我怀疑您的Python代码中可能只有一个错误。失败...
异步处理输出。如果出于性能原因考虑要与令牌化并行处理输出,则通常可以在第一个4K之后进行。只需在第二个线程中进行处理,而不是将行填充到队列中。如果你做不到...
这是一个可怕的技巧,但在某些情况下可能会起作用:将输入与虚拟输入散布在一起,这些虚拟输入会产生至少4K的输出。例如,您可以在每条实际输入行之后输出2047条空行(2047条CRLF加来自实际输出的CRLF = 4K),或b'A' * 4092 + b'\r\n'
的一行,以较快者为准。
根本不在此列表上的是前两个答案所建议的方法:将输出定向到Win32控制台并抓取该控制台。这是一个糟糕的主意,因为抓取使您将煮熟的输出显示为矩形字符阵列。刮板无法知道两条线本来是一条包裹起来的超长线。如果猜错了,您的输出将与您的输入不同步。如果您完全关心输出的完整性,则无法以这种方式解决输出缓冲。