Python子进程模块:循环遍历子进程的标准输出

6

我有一些使用子进程模块运行的命令。然后我想循环遍历输出的每一行。文档中说不要使用data_stream.stdout.read,虽然我没有使用它,但可能会调用类似的命令。我是这样循环输出的:

for line in data_stream.stdout:
   #do stuff here
   .
   .
   .

像从data_stream.stdout读取数据那样循环,会导致死锁吗?还是Popen模块设置了这种类型的循环,使其使用通信代码但为您处理所有调用?

4个回答

9

如果你在与子进程进行通信,即在写入stdin并从stdout读取时,你必须担心死锁问题。因为这些管道可能会被缓存,所以进行这种双向通信是非常不可取的:

data_stream = Popen(mycmd, stdin=PIPE, stdout=PIPE)
data_stream.stdin.write("do something\n")
for line in data_stream:
  ...  # BAD!

然而,如果在构建数据流时没有设置stdin(或stderr),那么您应该没问题。

data_stream = Popen(mycmd, stdout=PIPE)
for line in data_stream.stdout:
   ...  # Fine

如果需要双向通信,请使用communicate。该函数详细介绍请参见communicate文档

几乎正确,但是文档关于communicate的说明是:“读取的数据被缓存在内存中,因此如果数据大小很大或无限制,请勿使用此方法。” - Alex Martelli
所以,如果我从不干扰stderr或stdin,那么以这种方式循环是可以的。 - Matt
随着Python3.6+引入了asyncio子进程支持,我们可以同时拥有管道而不会面临死锁问题,请查看高级异步/等待asyncio API以创建和管理子进程 - Mohammad Moallemi
1
死锁来自低级管道缓存,而非线程 vs 异步,@MohammadMoallemi。 它们会对不同的操作系统产生不同的影响。 - Alice Purcell

6
这两个答案已经很好地抓住了问题的要点:不要混合写入子进程、读取它的输出、再次写入等操作——管道的缓冲意味着你有死锁的风险。如果可以的话,先将需要写入子进程的所有内容都写入,关闭该管道,然后再读取子进程的所有输出;如果数据量不太大,communicate非常适合此目的(如果数据量过大,您仍然可以通过“手动”方式实现相同的效果)。
如果您需要更精细的交互,请查看 pexpect 或者,如果您在Windows上,wexpect

4
SilentGhost/chrispy的回答对于你的子进程输出量较少到中等水平是可以的。但有时可能会有大量输出,过多以至于无法在内存中舒适地缓冲。在这种情况下,需要做的是启动(start())进程,并生成一些线程 - 一个用于读取子进程的stdout,另一个用于读取stderr,其中child是子进程。然后您需要等待wait()子进程终止。

实际上,这就是communicate()的工作原理;使用自己的线程的优点在于,您可以在生成时处理子进程的输出。例如,在我的项目python-gnupg中,我使用此技术来读取GnuPG可执行文件生成的状态输出,而不是通过调用communicate()等待所有输出。您可以检查此项目的源代码 - 相关内容位于模块gnupg.py中。


2
FYI,“communicate()”不会创建新线程,而是在POSIX上使用“select()”或“poll()”。 - musiphil

0

data_stream.stdout 是一个标准输出句柄。您不应该循环遍历它。communicate 返回元组(stdoutdata,stderr)。 您应该使用这个stdoutdata来完成您的任务。


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