39

我有一些执行外部应用程序的 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))

文档中有一些评论似乎表明了潜在的问题。在等待中,有:

警告:如果子进程生成足够多的输出到 a stdoutorstderr管道,从而阻塞等待 OS 管道缓冲区接受更多数据,这将死锁。用来communicate()避免这种情况。

虽然在沟通中,我看到:

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

所以我不清楚如果我有大量数据,我应该使用其中任何一个。他们没有说明在这种情况下我应该使用什么方法。

我确实需要 exec 的返回值,并解析和使用stdoutand stderr

那么,在 Python 中执行具有大量输出的外部应用程序的等效方法是什么?

4

7 回答 7

19

您正在阻止对两个文件的读取;第一个需要在第二个开始之前完成。如果应用程序向 写入了很多内容stderr,而对将——因为你在等待)。stdoutstdoutstderrstdout

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

最简单的就是不拦截stderr;离开stderr=None。错误将直接输出stderr。您不能拦截它们并将它们显示为您自己的消息的一部分。对于命令行工具,这通常是可以的。对于其他应用程序,这可能是一个问题。

另一种简单的方法是重定向stderrstdout,因此您只有一个传入文件: set stderr=STDOUT。这意味着您无法区分常规输出和错误输出。这可能会也可能不会接受,具体取决于应用程序如何写入输出。

处理此问题的完整而复杂的方法是select( http://docs.python.org/library/select.html )。这使您可以以非阻塞方式阅读:只要数据出现在stdout或上,您就可以获取数据stderr。如果真的有必要,我只会推荐这个。这可能在 Windows 中不起作用。

于 2009-07-24T23:23:35.690 回答
8

使用非常大的输出(即大量兆字节)独立读取stdout和读取:stderrselect

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)
于 2016-12-02T09:49:55.473 回答
6

很多输出都是主观的,所以很难做出推荐。如果输出量真的很大,那么您可能不想通过单个 read() 调用来获取所有内容。您可能想尝试将输出写入文件,然后像这样以增量方式提取数据:

f=file('data.out','w')
p = subprocess.Popen(cmd, shell=True, stdout=f, stderr=subprocess.PIPE)
errcode = p.wait()
f.close()
if errcode:
    errmess = p.stderr.read()
    log.error('cmd failed <%s>: %s' % (errcode,errmess))
for line in file('data.out'):
    #do something
于 2009-07-24T23:18:34.077 回答
6

Glenn Maynard 对僵局的评论是正确的。然而,解决这个问题的最好方法是两个创建两个线程,一个用于 stdout,一个用于 stderr,它们读取这些各自的流直到用尽,然后对输出做任何你需要的事情。

使用临时文件的建议可能对您有用,也可能对您不起作用,具体取决于输出的大小等,以及您是否需要在生成子进程的输出时对其进行处理。

正如 Heikki Toivonen 所建议的那样,您应该查看该communicate方法。但是,这会将子进程的 stdout/stderr 缓冲在内存中,并且您会从communicate调用中返回这些内容——这对于某些场景来说并不理想。但是通信方法的来源值得一看。

另一个示例是在我维护的包python-gnupg中,其中生成gpg可执行文件subprocess以执行繁重的工作,Python 包装器生成线程以读取 gpg 的 stdout 和 stderr 并在 gpg 生成数据时使用它们。您也可以通过查看那里的来源获得一些想法。在一般情况下,gpg 生成到 stdout 和 stderr 的数据可能非常大。

于 2009-07-25T19:14:53.703 回答
6

我有同样的问题。如果您必须处理大量输出,另一个不错的选择可能是为 stdout 和 stderr 使用文件,并按参数传递这些文件。

检查 python 中的 tempfile 模块:https ://docs.python.org/2/library/tempfile.html 。

像这样的东西可能会起作用

out = tempfile.NamedTemporaryFile(delete=False)

然后你会这样做:

Popen(... stdout=out,...)

然后您可以读取该文件,并稍后将其删除。

于 2014-07-24T20:28:30.003 回答
2

您可以尝试沟通,看看是否能解决您的问题。如果没有,我会将输出重定向到一个临时文件。

于 2009-07-24T23:24:10.260 回答
-1

这是捕获常规输出和错误输出的简单方法,所有这些都在 Python 中,因此stdout不适用:

com_str = 'uname -a'
command = subprocess.Popen([com_str], stdout=subprocess.PIPE, shell=True)
(output, error) = command.communicate()
print output

Linux 3.11.0-20-generic SMP Fri May 2 21:32:55 UTC 2014 

com_str = 'id'
command = subprocess.Popen([com_str], stdout=subprocess.PIPE, shell=True)
(output, error) = command.communicate()
print output

uid=1000(myname) gid=1000(mygrp) groups=1000(cell),0(root)
于 2018-03-29T05:41:11.857 回答