How to Achieve Continuous Interactive Dialog with a Subprocess in Python? [duplicate]

僤鯓⒐⒋嵵緔 提交于 2019-12-25 01:49:59

问题


Maybe continuous interactive isn't the right phrase. I'm wondering if someone could help me understand the basics of calling a program as a subprocess from a Python program? I've been hacking away but I keep running into frustrating errors. I understand best using simple examples. I have a program called square.py saved to my desktop that uses the following code:

i=0
while i<10:
    x=int(raw_input('Enter x-dimension: '))
    x1 = x*x
    print str(x1)
    i=i+1

Could someone please explain to me in simple terms how to call this program in IDLE and maintain continuous interactive dialogue with it (keep it open and running in the background) until it terminates on its own?

Eventually I will need to use this knowledge to call a genetic algorithm program written in C from a Python GUI (using tkinter). The genetic algorithm outputs an array of values, the user uses those values to do something and gives user feedback as to the utility of those values. The user feedback is in the form of 0-100. When the genetic algorithm receives the input it does its magic and outputs another array of numbers, which would hopefully have slightly better utility. So I want to wrap a Python GUI around a scary looking C program, feed the C program a feedback value and receive an array of numbers.

I hope I explained what I'm trying to do well enough; if anyone can help me use subprocess to call square.py, pass it a value and get back its output I would be pretty happy. Cheers!


回答1:


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



回答2:


"Continuous interactive" conflicts badly with the subprocess module, which uses OS-level pipes (on Unix-like systems; it necessarily does something a little different on Windows). To interact with a process the way users do on, e.g., ssh pty connections, however, you must create a pty session. Many programs assume, when talking to or via pipes, that they are not interactive.

The pexpect module is a translation of the old Don Libes expect into Python. It is aimed at this sort of idea.

The sh module also appears to have the necessary pieces to achieve the desired results.

(I have not used either one myself.)



来源:https://stackoverflow.com/questions/20185353/how-to-achieve-continuous-interactive-dialog-with-a-subprocess-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!