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

6
我正在使用 Python 的 subprocess 模块启动一个新进程。我想实时捕获新进程的输出,以便对其进行处理(显示、解析等)。我已经看到了许多可以做到这一点的示例,有些使用自定义文件对象,有些使用线程,还有一些尝试读取输出,直到进程完成。 文件对象示例(点击此处)
  • 我宁愿不使用自定义文件对象,因为我希望允许用户为 stdinstdoutstderr 提供他们自己的值。
线程示例(点击此处) 我并不真正理解为什么需要使用线程,所以我不想跟随这个例子。如果有人能够解释为什么线程示例是有意义的,我很乐意听取。然而,这个例子也限制了用户提供自己的 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

我的问题是,

Q. 有没有推荐的方法使用Python捕获进程的输出?


你可以给出一个输入和输出的样例吗? - aIKid
哈哈,就是这样!我在比较不同版本的Python输出。新手错误!!我已经从原始问题中删除了截断问题。谢谢你促使我再仔细看一下。 - Yani
1个回答

2
首先,你的设计有点傻,因为你可以这样做相同的事情:
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,它会为你处理所有的麻烦。
此外,即使对于你的模型来说这是可以接受的,你确定你可以在管道中排队那么多未读数据而不会阻塞子进程吗?在每个平台上都是如此吗?
仅仅将循环分解为在两者之间交替进行是无济于事的,因为你可能会在等待 5 分钟的同时被堵塞在 stdout.readline() 上,而此时 stderr 正在积累数据。
因此,你需要一种同时从两者中读取的方法。
如何同时从两个管道中读取数据?
这与同时处理1000个网络客户端的问题相同(但规模更小),它们有相同的解决方案:线程或多路复用(以及各种混合方法,例如在多路复用器和事件循环上使用绿色线程,或使用线程的Proactor等)。
线程版本的最佳示例代码是3.2+源代码中的communicate。它有点复杂,但如果您想要在Windows和Unix上正确处理所有边缘情况,那么确实无法避免一些复杂性。
对于多路复用,您可以使用select模块,但请注意,这仅适用于Unix(您无法在Windows上使用select管道),并且在没有3.2+(或subprocess32回退)的情况下存在错误,并且为了真正正确处理所有边缘情况,您需要向您的select添加信号处理程序。除非您真的非常不想使用线程,否则这是更困难的答案。
但是,简单的答案是使用别人的实现。 PyPI上有十几个以上的模块专门用于异步子进程。或者,如果您已经有一个围绕事件循环编写应用程序的好理由,几乎每个现代事件循环驱动的异步网络库(包括stdlib的asyncio)都包括开箱即用的子进程支持,在Unix和Windows上都可以使用。
有没有推荐的方法可以使用Python捕获进程输出?
这取决于你问的人是谁;一千个Python开发者可能会有一千种不同的答案……或至少有半打。如果你想知道核心开发人员会推荐什么,我可以猜测:
如果您不需要异步捕获它,请使用communicate(但请确保升级到至少3.2以获取重要的错误修复)。如果您需要异步捕获它,请使用asyncio(需要3.4)。

首先,我没有对这个答案进行负面评价。非常感谢您的解释。其次,我不确定是否要使用默认的io值(sys.stdout和sys.stderr),因为有些应用程序倾向于覆盖这些值(在我的情况下,是一个名为Maya的3D应用程序。它有自己的Python解释器,并使用某种自定义文件对象来进行io流)。提供subprocess.PIPE似乎可以确保一些一致性。我会看一下Python 3.2+中的示例。谢谢! - Yani
我发布了一个新问题,希望更具体地描述了我的问题。请看一下。感谢您的意见。https://dev59.com/1-o6XIcBkEYKwwoYKRDG - Yani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接