Reputation: 54
I am suffering from the Windows Python subprocess module.
This is test code1(named 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.')
and test code2(named test2.py
):
import random
import time
def r():
while True:
yield random.randint(0, 100)
for i in r():
print(i)
time.sleep(1)
Generally, the test code2 generates random integer(0~100) and print it out infinitely. I want the test code1 create a subprocess and launch it, read the stdout in realtime(not waiting for subprocess finished). But when I run the code, the output is :
python.exe test1.py
parent process
It blocks on stdout.read() forever. I have tried:
stdout.read
with communicate(), doesn't work as python doc expected, it will blocking until subprocess terminate.I searched a lot of similiar answers and did as they suggested(use stdout instead of communicate), but still didn't work?
Could anyone help me explaining why and how to do it?
This is my platform information:
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)] on win32
Upvotes: 1
Views: 6609
Reputation: 6341
It has to do with Python's output buffering (for a child process in your case). Try disabling the buffering and your code should work. You can do it by either running python with -u
key, or calling sys.stdout.flush()
.
To use the -u
key you need to modify the argument in the call to Popen
, to use the flush()
call you need to modify the test2.py
.
Also, your test1.py
would print just a single number, because you read only 1 byte from the pipe, instead of reading them in a loop.
Solution 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")
This way you don't have to touch the test2.py
at all.
Solution 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)
This will print each received byte on a new line, e.g.:
parent process
b'9'
b'5'
b'\n'
b'2'
b'6'
b'\n'
You can switch the pipe to text mode by providing encoding
in arguments or providing universal_newlines=True
, which will make it use the default encoding. And then write directly to sys.stdout
of your parent process. This will basically stream the output of a child process to the output of the parent process.
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")
This will provide the output as if the test2.py
is executed directly:
parent process
33
94
27
Upvotes: 4