【问题标题】:How to read stdout from python subprocess popen non-blockingly on Windows?如何在 Windows 上非阻塞地从 python 子进程 popen 读取标准输出?
【发布时间】:2018-06-23 20:37:21
【问题描述】:

我正在遭受 Windows Python 子进程模块的困扰。
这是测试代码1(命名为test1.py):

import subprocess as sbp


with sbp.Popen('python tests/test2.py',stdout=sbp.PIPE) as proc:
    print('parent process')
    print(proc.stdout.read(1))
    print('end.')

和测试代码2(命名为test2.py):

import random
import time

def r():
    while True:
        yield random.randint(0, 100)


for i in  r():
    print(i)
    time.sleep(1)

一般情况下,测试代码2会生成随机整数(0~100)并无限打印出来。 我希望测试代码1创建一个子进程并启动它,实时读取标准输出(不等待子进程完成)。 但是当我运行代码时,输​​出是:

python.exe test1.py
parent process

它永远阻塞在 stdout.read() 上。 我试过了:

  1. stdout.read 替换为communicate(),不会像python doc 预期的那样工作,它会阻塞直到子进程终止。
  2. 使用 poll() 方法检测子进程并读取 n 个字节,在 read() 上永远阻塞
  3. 修改test2.code,只生成一个nunber并打破循环。父进程立即打印出来(我认为是因为子进程终止了)

我搜索了很多类似的答案并按照他们的建议做了(使用标准输出而不是通信),但仍然没有工作?

谁能帮我解释一下为什么以及怎么做?

这是我的平台信息:
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)] on win32

【问题讨论】:

  • 有什么理由特别需要Popen?为什么不使用subprocess.check_output?还是check_output不做实时阅读?
  • @NiemaMoshiri 是的,check_output 类似于run,它会返回直到子进程终止。

标签: python python-3.x subprocess


【解决方案1】:

这与 Python 的输出缓冲有关(在您的情况下是子进程)。尝试禁用缓冲,您的代码应该可以工作。您可以通过使用-u 键运行python 或调用sys.stdout.flush() 来实现。

要使用-u 键,您需要修改对Popen 的调用中的参数,要使用flush() 调用,您需要修改test2.py

此外,您的 test1.py 将只打印一个数字,因为您只从管道中读取 1 个字节,而不是循环读取它们。

解决方案 1:

test1.py

import subprocess as sbp

with sbp.Popen(["python3", "-u", "./test2.py"], stdout=sbp.PIPE) as proc:
    print("parent process")
    while proc.poll() is None:  # Check the the child process is still running
        data = proc.stdout.read(1)  # Note: it reads as binary, not text
        print(data)
    print("end")

这样你就完全不用碰test2.py了。

解决方案 2:

test1.py

import subprocess as sbp

with sbp.Popen("./test2.py", stdout=sbp.PIPE) as proc:
    print("parent process")
    while proc.poll() is None:  # Check the the child process is still running
        data = proc.stdout.read(1)  # Note: it reads as binary, not text
        print(data)
    print("end")

test2.py

import random
import time
import sys

def r():
    while True:
        yield random.randint(0, 100)

for i in  r():
    print(i)
    sys.stdout.flush()  # Here you force Python to instantly flush the buffer
    time.sleep(1)

这将在新行上打印每个接收到的字节,例如:

parent process
b'9'
b'5'
b'\n'
b'2'
b'6'
b'\n'

您可以通过在参数中提供encoding 或提供universal_newlines=True 将管道切换到文本模式,这将使其使用默认编码。然后直接写信给你父进程的sys.stdout。这基本上会将子进程的输出流式传输到父进程的输出。

test1.py

import subprocess as sbp
import sys

with sbp.Popen("./test2.py", stdout=sbp.PIPE, universal_newlines=True) as proc:
    print("parent process")
    while proc.poll() is None:  # Check the the child process is still running
        data = proc.stdout.read(1)  # Note: it reads as binary, not text
        sys.stdout.write(data)
    print("end")

这将提供就像直接执行 test2.py 一样的输出:

parent process
33
94
27

【讨论】:

  • 你指出关键——子进程输出缓冲!谢谢 Nikita,这两种解决方案都很棒。这是用于在子进程和父进程之间传输数据(大数据)的情况的测试代码。所以字节格式是可以的,你在循环中使用 poll() 的例子也适用于我。再次感谢您!
猜你喜欢
  • 1970-01-01
  • 2011-09-18
  • 2014-03-29
  • 2016-07-28
  • 1970-01-01
  • 2017-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多