使用python捕获进程输出的最佳方法是什么?

时间:2014-01-14 02:22:15

标签: python subprocess stdout

我正在使用python的subprocess模块来启动一个新进程。我想实时捕获新进程的输出,以便我可以用它做事(显示它,解析它等)。我已经看到很多关于如何做到这一点的例子,有些使用自定义文件类对象,有些使用threading,有些尝试读取输出直到进程完成。

File Like Objects Example (click me)

  • 我不想使用自定义文件类对象,因为我希望允许用户为stdinstdoutstderr提供自己的值。

Threading Example (click me)

  • 我真的不明白为什么需要线程,所以我不愿意遵循这个例子。如果有人可以解释为什么线程示例有意义我会很高兴听。但是,此示例还限制用户提供自己的stdoutstderr值。

读取输出示例(见下文)

对我来说最有意义的例子是阅读stdoutstderr,直到过程结束。以下是一些示例代码:

import subprocess

# Start a process which prints the options to the python program.
process = subprocess.Popen(
                           ["python", "-h"],
                           bufsize=1,
                           stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           )    

# While the process is running, display the output to the user.
while True:

    # Read standard output data.
    for stdout_line in iter(process.stdout.readline, ""):

        # Display standard output data.
        sys.stdout.write(stdout_line)

    # Read standard error data.
    for stderr_line in iter(process.stderr.readline, ""):

        # Display standard error data.
        sys.stderr.write(stderr_line)

    # If the process is complete - exit loop.
    if process.poll() != None:
        break

我的问题是,

问。是否建议使用python捕获进程输出?

1 个答案:

答案 0 :(得分:-1)

首先,你的设计有点傻,因为你可以做同样的事情:

process = subprocess.Popen(
                           ["python", "-h"],
                           bufsize=1,
                           stdout=sys.stdout,
                           stderr=sys.stderr
                           )

...或者,甚至更好:

process = subprocess.Popen(
                           ["python", "-h"],
                           bufsize=1
                           )

但是,我认为这只是一个玩具示例,你可能想要做一些更有用的事情。


您的设计的主要问题是,在stderr完成之前,它不会从stdout读取任何内容。

想象一下,您正在驾驶一个MP3播放器,将每个曲目名称打印到stdout,并将信息记录到stderr,并且您想要播放10首歌曲。你真的想在向用户显示任何日志记录之前等待30分钟吗?

如果 可以接受,那么您也可以使用communicate,它可以解决您的所有麻烦。

另外,即使您的模型可以接受,您确定可以在管道中排队那么多未读数据而不会阻塞孩子吗?在每个平台上?

只要将循环拆分为两者之间的替换就无济于事,因为在stdout.readline()堆积的同时,你可能会在stderr上阻塞5分钟。

这就是为什么你需要一些方法来同时阅读两者。


你如何一次读取两个管道?

这与同时处理1000个网络客户端的问题相同(但更小),并且它具有相同的解决方案:线程或多路复用(以及各种混合,如在多路复用器和事件循环之上执行绿色线程,或使用螺纹式预紧器等。)

线程版本的最佳示例代码是来自3.2+源代码的communicate。这有点复杂,但如果你想在Windows和Unix上正确处理所有边缘情况,那么实际上并没有避免一点复杂性。

对于多路复用,您可以使用select模块,但请记住,这仅适用于Unix(Windows上的管道上不能select),没有3.2+的情况下它是错误的(或者subprocess32 backport),并且要真正得到所有边缘情况,你需要为select添加一个信号处理程序。除非你真的,真的不想使用线程,这是更难的答案。

easy 的答案是使用其他人的实现。 PyPI上有十几个或更多模块专门用于异步子进程。或者,如果您已经有充分的理由围绕事件循环编写应用程序,那么几乎所有现代事件循环驱动的异步网络库(包括stdlib的asyncio)都包含开箱即用的子进程支持,在Unix和Windows上。


  

是否存在使用python捕获进程输出的推荐方法?

这取决于你问的是谁;一千个Python开发人员可能有一千个不同的答案......或者至少有六个。如果你问的是核心开发者会推荐什么,我可以猜一下:

如果您不需要异步捕获它,请使用communicate(但请确保至少升级到3.2以进行重要的错误修复)。如果确实需要异步捕获,请使用asyncio(需要3.4)。