繁体   English   中英

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

[英]Subprocess, repeatedly write to STDIN while reading from STDOUT (Windows)

我想从 python 调用一个外部进程。我调用的进程读取一个输入字符串并给出标记化结果,然后等待另一个输入(如果有帮助,二进制是 MeCab 标记器)。

我需要通过调用此过程来标记数千行字符串。

问题是Popen.communicate()工作但在给出 STDOUT 结果之前等待进程结束。 我不想继续关闭和打开新的子流程数千次。 (而且我不想发送整个文本,将来它很容易超过数万行。)

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()而不是使用 communicate 但它被stdin阻止并且在proc.stdin.close()之前不返回任何结果。 这又意味着我每次都需要创建一个新流程。

我试图从一个类似的问题中实现队列和线程,如下所示,但它要么不返回任何东西,因此它停留在While True上,或者当我通过重复发送字符串强制 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。

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

(...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

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

唯一的问题是 conmu 是一个 gui 应用程序; 因此,如果没有其他方法可以连接到其输入和输出,则可能必须从源代码(它是开源的)进行调整和重建。 我还没有找到任何其他方式; 但这应该有效。

我曾问到某种控制台模式下运行的问题在这里; 所以你也可以检查那个线程的东西。 作者马克西姆斯在 SO...

编码

while True:
    try:
        line = q.get_nowait()
    except Empty:
        pass
    else:
        print(line)
        break

本质上是一样的

print(q.get())

除了效率较低,因为它在等待时会消耗 CPU 时间。 显式循环不会使来自子进程的数据更快到达; 它到了就到了。

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

  1. 找到一个 Python 库并使用它。 MeCab 源代码树中似乎有一个官方的 Python 绑定,我在 PyPI 上看到了一些预构建的包。 您还可以查找可以使用ctypes或其他 Python FFI 调用的 DLL 构建。 如果那不起作用...

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

  3. 构建您自己的二进制文件,在每行之后刷新。 找到主循环并在其中插入刷新调用应该很容易。 但是MeCab 似乎已经明确刷新了,并且 git blame 说刷新语句最后一次更改是在 2011 年,所以我很惊讶您曾经遇到过这个问题,并且我怀疑您的 Python 代码中可能只是一个错误。 失败了...

  4. 异步处理输出。 如果您担心出于性能原因想要与标记化并行处理输出,您通常可以在第一个 4K 之后这样做。 只需在第二个线程中进行处理,而不是将行塞入队列。 如果你做不到那...

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

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

我想答案,如果不是解决方案,可以在这里找到https://github.com/ikriv/ConsoleProxy/blob/master/src/Tools/Exec/readme.md

我想,因为我有一个类似的问题,我解决了这个问题,并且无法尝试这条路线,因为这个工具不适用于 Windows 2003,这是我必须使用的操作系统(在遗留应用程序的 VM 中)。

我想知道我是否猜对了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM