Programs that are designed to interact with a human are different from programs that are designed to interact with other programs. Your square.py
script is closer to the former category. Possible issues:
- the prompt ends in the middle of a line that forces a parent script to read one byte at a time instead of full lines or known chunks to avoid blocking
- child's answers are not flushed explicitly. It means that the block buffering may be enabled in non-interactive mode e.g., when it is run via
subprocess
without pty
Here's how you could interact with it using subprocess
module in its current form:
#!/usr/bin/env python
from __future__ import print_function
import sys
from itertools import cycle
from subprocess import Popen, PIPE
from textwrap import dedent
# start child process
p = Popen([sys.executable or 'python', '-u', '-c', dedent("""
for i in range(10):
x = int(input('Enter x-dimension: '))
print(x*x)
""")], stdin=PIPE, stdout=PIPE, universal_newlines=True, bufsize=1)
for n in cycle([3, 1, 4, 15, 926]): # infinite loop
while p.poll() is None: # while the subprocess is running
# send input to the child
print(n, file=p.stdin)
# read & parse answer
data = p.stdout.readline().rpartition(' ')[2]
if not data: # EOF
answer = None
break # exit inner loop
answer = int(data)
if answer == 1: # show example when input depends on output
n += 1
else: # done with given `n`
break # exit inner loop
else: # subprocess ended
break # exit outer loop
if answer is not None:
print("Input %3d Output %6d" % (n, answer))
p.communicate() # close pipes, wait for the child to terminate
And here's the same thing but using pexpect
(for comparison):
#!/usr/bin/env python
import sys
from itertools import cycle
from textwrap import dedent
import pexpect
child = pexpect.spawnu(sys.executable or 'python', ['-c', dedent("""
for i in range(10):
x = int(input('Enter x-dimension: '))
print(x*x)
""")])
for n in cycle([3, 1, 4, 15, 926]):
while True:
i = child.expect([pexpect.EOF, u'x-dimension:'])
if i == 0: # EOF
answer = None
child.close()
sys.exit()
elif i == 1: # child waits for input
child.sendline(str(n))
child.expect(u'\\n\\d+\\s')
answer = int(child.after)
if answer == 1:
n += 1
else:
break
else:
assert 0
else: # child terminated
break
if answer is not None:
print("Input %3d Output %6d" % (n, answer))
Both scripts are written to support Python 2 and Python 3 from the same source.
Note: there is -u
argument in the subprocess
-based script that allows to read the lines as soon as they are available even in non-interactive mode. pexpect
-based script works without such switch. stdio
-based programs can be unbuffered/make line-buffered using stdbuf
, unbuffer
utilities or by providing a pty.
You can see that even the simplest child script (square.py
) requires to overcome several issues to even work at all.
Everything is simpler when the child program expects to be run from another program while remaining human-readable (debuggable). In this case, square.py
could look like:
#!/usr/bin/env python
import sys
import time
for line in iter(sys.stdin.readline, ''): # get line as soon as it is available
print(int(line)**2) # find square
sys.stdout.flush() # make the answer available immediately
time.sleep(.5) # a delay to show that the answer is available immediately
It could be used from a subprocess
-based module in "all at once" mode:
import sys
from subprocess import Popen, PIPE
L = [2, 7, 1] # numbers to be squared
p = Popen([sys.executable or 'python', 'square.py'], stdin=PIPE, stdout=PIPE,
universal_newlines=True, bufsize=-1)
answers = map(int, p.communicate("\n".join(map(str, L)))[0].splitlines())
Or one number at a time:
#!/usr/bin/env python
import sys
from subprocess import Popen, PIPE
answers = []
p = Popen([sys.executable or 'python', 'square.py'], stdin=PIPE, stdout=PIPE,
bufsize=1)
for c in [b'2', b'7', b'1']:
p.stdin.write(c + b'\n')
p.stdin.flush()
answers.append(int(p.stdout.readline()))
print(answers)
p.communicate() # close pipes, wait for child to finish
print(answers)
To get an array from your C program; you could json
module:
import json
from subprocess import Popen, PIPE
p = Popen(['./c-program', 'other', 'args'], stdin=PIPE, stdout=PIPE, bufsize=1)
p.stdin.write(json.dumps({'parameter': 8}).encode() + b'\n') # send input
p.stdin.flush()
result = json.loads(p.stdout.readline().decode()) # e.g., {"result": [0, 0, 7]}
# ...
p.communicate() # close pipes, wait for child to finish