Python subprocess调用子进程时如何实时获取stdout输出
问题原因
你无法实时获取child.py输出的核心原因是Python标准输出的缓冲机制:
- 当程序stdout直接连接终端时,默认使用行缓冲,每打印完一行就会立刻把内容推送到输出流
- 当程序stdout被管道接管(也就是
subprocess.PIPE对接的场景),默认使用块缓冲,会攒够一定大小(通常是4KB)的内容才会一次性推送,程序退出时也会强制刷新缓冲,这就是你之前要等child.py跑完5秒才能读到所有输出的根本原因。
解决方案
你可以根据自己能不能修改子进程代码,二选一即可。
方案1:无需修改child.py,仅调整父进程启动参数
给Python解释器加-u参数,强制关闭子进程的stdout、stderr缓冲,同时给Popen开启行缓冲模式即可,修正后的main.py代码如下:
import subprocess process = subprocess.Popen( # 用python3 -u启动脚本,强制无缓冲输出 ["python3", "-u", "./child.py"], stdout=subprocess.PIPE, bufsize=1, # 管道开启行缓冲 text=True # 直接返回字符串而非字节对象,省去解码步骤 ) # 循环读取直到子进程退出,不要写死循环次数避免漏读输出 while True: output_line = process.stdout.readline() if not output_line: break # 读取的行自带换行符,用end=''避免打印多余空行 print(output_line, end='')
这个方案改完直接运行,就能每秒实时拿到child.py打印的ok 1到ok 4内容,不需要等子进程全部执行完。
方案2:修改child.py,打印时主动刷新缓冲
如果你不方便改父进程的启动逻辑,可以在child.py的print语句里加flush=True参数,强制每次打印完立刻刷新缓冲,修正后的child.py代码如下:
#!/usr/bin/env python3 # coding=utf-8 import time def run(): i = 1 while i < 5: time.sleep(1) # 加flush=True,打印后立刻刷出缓冲,不需要等攒块 print(f'ok {i}', flush=True) i+=1 if __name__ == "__main__": run()
这个方案下你原来的main.py不需要大改,只要去掉写死的循环次数逻辑,正常读行就能拿到实时输出。
额外注意事项
- 不要用
process.communicate()读取实时输出,这个方法的设计就是等子进程完全退出后才一次性返回所有输出,天生不适合流式读取场景 - 如果你的子进程不是Python程序,在Linux/macOS环境下可以用系统自带的
stdbuf -oL 你的子进程命令强制让子进程stdout按行刷新,不需要改子进程代码 readline()本身是阻塞调用,有新行到达时会立刻返回,不需要额外加sleep等待,加多余的sleep反而会导致输出延迟
内容的提问来源于stack exchange,提问作者danilo
相关产品推荐
相关产品推荐

