使用subprocess.Popen处理大量输出的进程

41 投票
7 回答
30330 浏览
提问于 2025-04-15 13:08

我有一些Python代码,它可以运行一个外部应用程序。当这个应用程序的输出很少时,一切都正常,但如果输出很多,就会卡住。我的代码是这样的:

p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
errcode = p.wait()
retval = p.stdout.read()
errmess = p.stderr.read()
if errcode:
    log.error('cmd failed <%s>: %s' % (errcode,errmess))

文档中有一些评论似乎提到了可能的问题。在“wait”部分,有这么一句:

警告:如果子进程生成的输出太多,导致等待操作系统的管道缓冲区接受更多数据时发生死锁。这种情况下请使用communicate()来避免。

而在“communicate”部分,我看到:

注意:读取的数据会缓存在内存中,所以如果数据量很大或没有限制,请不要使用这种方法。

所以我不太清楚在面对大量数据时应该用哪种方法。这两者都没有说明我在这种情况下该用什么。

我确实需要从执行中获取返回值,并且会解析和使用stdoutstderr的内容。

那么在Python中,有什么等效的方法可以用来执行一个输出量大的外部应用程序呢?

7 个回答

6

Glenn Maynard在他的评论中提到的死锁问题是对的。不过,解决这个问题的最好方法是创建两个线程,一个用来处理标准输出(stdout),另一个用来处理错误输出(stderr),这两个线程会分别读取这些输出,直到没有数据为止,然后你可以根据需要处理这些输出。

使用临时文件的建议可能对你有用,也可能没用,这取决于输出的大小等因素,以及你是否需要实时处理子进程生成的输出。

正如Heikki Toivonen所建议的,你应该看看communicate这个方法。不过,这个方法会把子进程的标准输出和错误输出都存储在内存中,然后你从communicate调用中得到这些数据——在某些情况下,这并不是最理想的选择。不过,了解communicate方法的源代码是很有价值的。

另一个例子是在我维护的一个包中,python-gnupg,在这个包里,gpg可执行文件是通过subprocess启动的,用来完成繁重的工作,而Python的包装器则会启动线程来读取gpg的标准输出和错误输出,并在gpg生成数据时进行处理。你也许可以通过查看那里的源代码获得一些灵感,因为gpg生成的标准输出和错误输出在一般情况下可能会非常大。

10

在处理非常大的输出(比如几兆字节的数据)时,如何独立读取 stdout(标准输出)和 stderr(错误输出),可以使用 select 这个工具。

import subprocess, select

proc = subprocess.Popen(cmd, bufsize=8192, shell=False, \
    stdout=subprocess.PIPE, stderr=subprocess.PIPE)

with open(outpath, "wb") as outf:
    dataend = False
    while (proc.returncode is None) or (not dataend):
        proc.poll()
        dataend = False

        ready = select.select([proc.stdout, proc.stderr], [], [], 1.0)

        if proc.stderr in ready[0]:
            data = proc.stderr.read(1024)
            if len(data) > 0:
                handle_stderr_data(data)

        if proc.stdout in ready[0]:
            data = proc.stdout.read(1024)
            if len(data) == 0: # Read of zero bytes means EOF
                dataend = True
            else:
                outf.write(data)
19

你正在从两个文件中读取数据,而第一个文件的读取必须在第二个文件开始之前完成。如果你的程序往 stderr(错误输出)写了很多东西,而没有往 stdout(标准输出)写任何东西,那么你的程序就会一直在等 stdout 的数据,但实际上这些数据根本不会到来。同时,你运行的程序也在等它写入 stderr 的内容被读取,但由于你在等 stdout,所以这些内容永远不会被读取。

有几种方法可以解决这个问题。

最简单的方法是不要拦截 stderr,直接把它设为 stderr=None。这样错误信息会直接输出到 stderr,你无法将它们拦截并作为自己消息的一部分显示出来。对于命令行工具来说,这通常是可以的,但对于其他应用程序来说,可能会有问题。

另一个简单的方法是把 stderr 重定向到 stdout,这样你就只有一个输入文件了:设置 stderr=STDOUT。这意味着你无法区分正常输出和错误输出。是否可以接受这点,取决于应用程序是如何输出内容的。

处理这个问题的完整而复杂的方法是使用 selecthttp://docs.python.org/library/select.html)。这个方法可以让你以非阻塞的方式读取数据:只要 stdoutstderr 有数据出现,你就能读取到。只有在真的必要时,我才会推荐使用这个方法。而且,这个方法在 Windows 系统上可能不太好用。

撰写回答