使用subprocess.Popen调用Python脚本并刷新数据

13 投票
1 回答
24297 浏览
提问于 2025-04-17 13:21

好吧,我看到很多类似的帖子,但没有一个给出完整的答案,而且我尝试过的所有方法都对我没用。

1) 一个不断输出数据并刷新输出的脚本:

import time
import sys

if __name__ == '__main__':
    for i in range(5):
        print i,
        sys.stdout.flush()
        time.sleep(1)

2) 一个用Popen调用第一个脚本的脚本,应该是一个一个打印数字,但由于某种原因,它一次性打印所有数字:

import sys
import subprocess

if __name__ == '__main__':
    process = subprocess.Popen(['python', 'flush.py'], stdout = subprocess.PIPE )
    for line in iter(process.stdout.readline, ''):
        print line,
        sys.stdout.flush()

我有点困惑的是,在第一个脚本中,如果你去掉刷新(flush),它会把输出全部放在一行里 O_O... 我很确定这是因为time.sleep的原因,但我还是希望它能像标准输出那样不断返回值0,1,2,3,4,而不是一次性返回。当然,刷新解决了这个问题,但对我来说这还是有点奇怪……

主要问题:是第二个脚本没有一个一个返回数字,而是一次性返回所有数字……我需要的是看到数字一个一个地出现……

我在某个地方读到,它没有返回EOF(文件结束符),而Popen在等待这个EOF来关闭管道,所以它一直运行到最后……

那么我接下来该做什么或者尝试什么呢?提前谢谢你。

1 个回答

14

正如@Warren Weckesser的评论所说,你的问题和缓冲问题没有关系。

在父进程中,.readline()方法会等到读取到换行符或者文件结束符(EOF)才会返回。你的子进程根本没有打印出任何换行符,所以父进程在子进程结束之前不会打印出任何内容

最简单的解决办法就是在子脚本中的print i,后面去掉逗号。

这样也可以:

#!/usr/bin/env python
import sys
from subprocess import Popen, PIPE

p = Popen([sys.executable or 'python',
           '-u', # unbuffer stdout (or make it line-buffered on Python 3)
           '-c',
           """
import time

for i in range(5):
    print(i) # <-- no comma i.e., each number is on its own line
    time.sleep(1)
"""], stdout=PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print(int(line)**2)

举个例子:

 $ python parent.py
 0
 1
 4
 9
 16

这些数字每秒都会打印一次,而不需要等到子进程结束。

如果你不想修改子脚本,那么你可以使用readline()方法,它会在遇到空格而不是换行符时停止,比如:

#!/usr/bin/env python
import sys
from subprocess import Popen, PIPE

p = Popen(['python2', 'child.py'], stdout=PIPE, bufsize=0)
for token in generate_tokens(p.stdout):
    print(int(token))

这里generate_tokens()会生成以空格分隔的标记:

def generate_tokens(pipe):
    buf = []
    while True:
        b = pipe.read(1) # read one byte
        if not b: # EOF
            pipe.close()
            if buf:
                yield b''.join(buf)
            return
        elif not b.isspace(): # grow token
            buf.append(b)
        elif buf: # full token read
            yield b''.join(buf)
            buf = []

这样一来,整数一旦被子进程打印出来,就会立即显示。

撰写回答