从子进程获取进度信息

25 投票
3 回答
16133 浏览
提问于 2025-04-16 11:32

我想启动一个需要几分钟才能完成的程序。在这段时间里,我想查看程序的进度信息(这些信息会打印在标准输出上)。问题是,我找不到在程序运行时读取输出的方法。

我找到的唯一可以读取程序输出的函数是 Popen.communicate(),但这个方法会等到程序结束后才返回结果。所以我无法在程序运行时获取进度,并以特定的格式展示给用户。

有没有其他方法可以做到这一点呢?

当我用 subprocess.popen 运行这个程序时,我可以在屏幕上看到程序的输出。有没有办法把这个输出隐藏起来?(Ubuntu 10.10,普通终端)

3 个回答

1

这完全是可能的:我的包 python-gnupg 就是这样做的,它在一个子进程中启动 gpg(Gnu Privacy Guard)。一般来说,你需要为子进程的标准输出和错误输出指定 subprocess.PIPE;然后创建两个独立的线程,分别读取子进程的标准输出和错误输出,并把它们发送到你想要的地方。

python-gnupg 的情况下,gpg 的状态信息会在 gpg 进程运行时被读取和处理(而不是等到它完成后再处理)。

基本上,伪代码是

process = subprocess.Popen(..., stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stderr = process.stderr
rr = threading.Thread(target=response_reader_func, args=(process.stderr,))
rr.setDaemon(True)
rr.start()

dr = threading.Thread(target=data_reader_func, args=(process.stdout,))
dr.setDaemon(True)
dr.start()

dr.join()
rr.join()
process.wait()

这些读取函数通常是一个封装类的方法,它们会根据读取到的内容做出相应的处理(在你的情况下,可能是以某种方式更新进度信息)。

3

你可以对你的子进程的状态进行轮询,并持续输出结果。

p = subprocess.Popen('ls;sleep 10', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

rc = p.poll()
while rc != 0:
    while True:
        line = p.stdout.readline()
        if not line:
            break
        print line
    rc = p.poll()

assert rc == 0
23

最简单的方法是用关键字参数 stdout=subprocess.PIPE 来调用 Popen。

p = subprocess.Popen(["ls"], stdout=subprocess.PIPE)
while True:
    line = p.stdout.readline()
    if not line:
        break
    print line

为了让你更好地理解,这里有两个示例脚本。把它们放在同一个文件夹里,然后运行 python superprint.py

第一个脚本是 printandwait.py:

import time
import sys
print 10
sys.stdout.flush()
time.sleep(10)
print 20
sys.stdout.flush()

第二个脚本是 superprint.py:

import subprocess
import sys
p = subprocess.Popen(["python printandwait.py"], shell=True, stdout=subprocess.PIPE)
while True:
    print "Looping"
    line = p.stdout.readline()
    if not line:
        break
    print line.strip()
    sys.stdout.flush()

撰写回答