使用标准输入输出在两个进程之间进行通信

3

我想编写一个简单的脚本(A),用于执行外部脚本(B)

  • A应该通过写入其stdin并读取其stdout与B进行通信
  • B应该读取其stdin并打印它

所有这些都应该在不关闭流的情况下完成

A.py

import subprocess
process = subprocess.Popen(['python', 'B.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
for _ in range(3):
    process.stdin.write(b'hello')
    print(process.stdout.read())

B.py

import sys

for line in sys.stdin:
    print(line)

输出结果应该是:
>>> b'hello'
>>> b'hello'
>>> b'hello'

问题在于A只是等待。
print(process.stdout.read())

如果我修改A,添加close():
for _ in range(3):
    process.stdin.write(b'hello')
    process.stdin.close()
    print(process.stdout.read())

我明白了:

>>> b'hello\n'
>>> Traceback (most recent call last):
>>>   File "A.py", line 7, in <module>
>>>     process.stdin.write(b'hello')
>>> ValueError: write to closed file
1个回答

7

使用communicate()

Python已经实现了communicate()方法(它可以去到A.pyB.py也可以)。然而,它只适用于简单通信(你知道前面要发送的数据是什么),如果你需要一个更复杂的通信,比如:

send data to process B
read stdout
if stdout ...
    do something bases on stdout
    write to stdin

你需要实现自己的communicate(),原始实现在这里


逐步操作

我已经逐步测试和调试过这个内容,以下是发生的情况:

# For Popen(bufsize!=0)
A: process.stdin.write(b'hello\r\n')
B: line = sys.stdin.readline() # Hangs

所以在添加了 bufsize=0 (无缓冲)之后
# Popen(bufsize=0)
A: process.stdin.write(b'hello\r\n') # Without \r\n B still hangs
B: line = sys.stdin.readline()
B: print('Send back', line.strip()) # Without strip it prints empty line
A: process.stdout.readline() # Hangs

那么什么有效?

# Popen(bufsize=0)
A: process.stdin.write(b'hello\r\n')
B: line = sys.stdin.readline()
B: print('Send back', line.strip())
B: sys.stdout.flush()
A: process.stdout.readline()

解释

您已将缓冲区设置为io.DEFAULT_BUFFER_SIZE(通常为4090B)。来自文档:

bufsize将作为创建stdin / stdout / stderr管道文件对象时io.open()函数的相应参数提供:0表示无缓冲(读取和写入是一个系统调用,并且可以返回短),1表示行缓冲,任何其他正数值表示使用大约该大小的缓冲区。负的bufsize(默认值)意味着将使用io.DEFAULT_BUFFER_SIZE的系统默认值。

因此,首先A不会刷新,因为它尚未填满其缓冲区,因此B正在等待。在Windows下,不能简单地使用process.stdin.flush(),因此必须使用bufsize = 0

同样,编写os.linesep (\r\n)也很重要,因为它涉及到readline()

注意:我认为使用bufsize=1(行缓冲)也应该可以工作,但事实并非如此。我不知道为什么。

然后在B中当它不刷新sys.stdout时,相同的情况发生了,让我感到惊讶的是,B:sys.stdout没有被设置为无缓冲,因为:

bufsize将作为相应的参数提供给io.open()函数,在创建stdin/stdout/stderr管道文件对象时使用。

无论如何,你必须在B中调用sys.stdout.flush()

它可以通过close()工作,因为它强制执行flush()


给我代码

A.py:

import subprocess
import sys

process = subprocess.Popen([sys.executable, r'B.py'], stdin=subprocess.PIPE, 
                            stdout=subprocess.PIPE, bufsize=0)
for _ in range(3):
    process.stdin.write(b'hello\r\n')
    print(process.stdout.readline())

B.py:

import sys

for line in sys.stdin:
    print('Send back', line.strip())
    sys.stdout.flush()

加上 \n 后,它仍然像以前一样在等待。 - Ofer Helman
尝试添加\r\n,但不成功。当添加flush时,第二次迭代出现以下错误:IOError: [Errno 22] Invalid argument。 - Ofer Helman
哇,太棒了,这个答案对我也有用,只是稍微改动了一下,在 A 中写入后我进行了刷新,而在 B 中则没有。我正在使用 Python3.2,这可能是区别所在吗? - Ofer Helman
1
@OferHelman 我正在运行 Python 3.2.5 (default, May 15 2013, 23:07:10) [MSC v.1500 64 bit (AMD64)] on win32,使用的是Windows 7系统,可能与版本有关。我觉得让这些东西正常工作有点神奇。 - Vyktor
有趣,非常类似于我的设置,我会在你的答案中添加适合我的排列。 - Ofer Helman
简单来说,打开终端运行A.py(指定B.py的位置),它会挂起。打开新终端,运行B.py,它也会挂起。那么如何测试它呢? - nish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接