【问题标题】:Live reading / writing to a subprocess stdin/stdout实时读取/写入子进程标准输入/标准输出
【发布时间】:2014-10-16 20:43:59
【问题描述】:

我想为另一个命令行程序制作一个 Python 包装器。

我想尽快阅读Python的stdin,过滤翻译,然后及时写入子程序的stdin

同时,我想尽快从子程序的stdout 中读取,并在经过一番按摩后,立即将其写入Python 的stdout

Python 子进程模块充满了使用communicate() 来避免死锁的警告。但是,communicate() 不允许我访问子程序的 stdout,直到子程序终止。

【问题讨论】:

标签: python io subprocess


【解决方案1】:

我认为您可以(小心地)忽略使用Popen.stdin 等的警告。只需确保逐行处理流并以公平的时间表遍历它们,以免填满任何缓冲区。在 Python 中执行此操作的一种相对简单(且效率低下)的方法是为三个流使用单独的线程。这就是Popen.communicate 在内部进行的方式。查看其源代码以了解具体方法。

【讨论】:

  • 正如下面@Vorticity 所指出的,附加到PIPE 的程序通常会缓冲它们的输出。为了解决这个问题,我使用pty.openpty 为孩子的标准输出创建了一个主从。您将 subprocess.Popen 的标准输出设置为从属,并使用线程从主控读取。
  • @Will 哦,好吧,那是你的问题。抱歉,我没听明白,但你的解决方案听起来不错。
  • @Will:除非您非常了解警告,否则不要忽略警告,否则通过所有测试的程序可能会在生产中挂起(这不难理解,只要确保您这样做)。 .communicate() 仅在 Windows 上使用线程,否则为 select loop is used
  • @J.F.Sebastian 感谢警告。我现在掌握了情况。我知道幕后发生了什么。
【解决方案2】:

免责声明:此解决方案可能要求您有权访问您尝试调用的进程的源代码,但无论如何可能值得一试。这取决于被调用的进程定期刷新其stdout 缓冲区,这不是标准的。

假设您有一个由subprocess.Popen 创建的进程procproc 具有属性 stdinstdout。这些属性只是类似文件的对象。因此,为了通过stdin 发送信息,您可以致电proc.stdin.write()。要从 proc.stdout 检索信息,您可以调用 proc.stdout.readline() 来读取单个行。

几个注意事项:

  • 当通过write() 写入proc.stdin 时,您需要以换行符结束输入。如果没有换行符,您的子进程将挂起,直到通过换行符。
  • 为了从proc.stdout 读取信息,您需要确保子进程调用的命令在每个打印语句之后适当地刷新其标准输出缓冲区,并且每行都以换行符结尾。如果标准输出缓冲区没有在适当的时间刷新,您对 proc.stdout.readline() 的调用将挂起。

【讨论】:

  • 我发现我可以通过给大多数子程序提供pty.openpty() 标准输出来欺骗它们。这会诱使他们进行行缓冲而不是大块缓冲。