子进程,从STDOUT读取时重复写入STDIN(Windows)

时间:2017-03-24 04:04:14

标签: python windows python-3.x subprocess mecab

我想从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 ...)

感谢。

3 个答案:

答案 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时间。显式循环不会使子流程中的数据更快到达;它到达时就到达。

对于处理不合作的二进制文件,我有一些建议,从最佳到最坏:

  1. 找到一个Python库并改用它。看来MeCab源代码树中有an official Python binding,我在PyPI上看到了一些预构建的软件包。您还可以查找可以用ctypes或其他Python FFI调用的DLL构建。如果那不起作用...

  2. 找到在输出的每一行之后刷新的二进制文件。我在网上找到的最新Win32版本v0.98会在每行之后刷新。失败...

  3. 构建自己的二进制文件,每行之后刷新一次。找到主循环并在其中插入刷新调用应该足够容易。但是MeCab seems to explicitly flush already和git blame表示flush语句最后一次更改是在2011年,因此令您感到惊讶的是我曾经感到这个问题,并且我怀疑您的Python代码中可能只有一个错误。失败...

  4. 异步处理输出。如果出于性能原因考虑要与令牌化并行处理输出,则通常可以在第一个4K之后进行。只需在第二个线程中进行处理,而不是将行填充到队列中。如果你做不到...

  5. 这是一个可怕的技巧,但在某些情况下可能会起作用:将输入与虚拟输入散布在一起,这些虚拟输入会产生至少4K的输出。例如,您可以在每条实际输入行之后输出2047条空行(2047条CRLF加来自实际输出的CRLF = 4K),或b'A' * 4092 + b'\r\n'的一行,以较快者为准。

根本不在此列表上的是前两个答案所建议的方法:将输出定向到Win32控制台并抓取该控制台。这是一个糟糕的主意,因为抓取使您将煮熟的输出显示为矩形字符阵列。刮板无法知道两条线本来是一条包裹起来的超长线。如果猜错了,您的输出将与您的输入不同步。如果您完全关心输出的完整性,则无法以这种方式解决输出缓冲。