通过stdin和stdout进行两个进程通信
我想写一个简单的脚本(A),它可以执行另一个外部脚本(B)。
- A需要通过写入B的标准输入(stdin)来和B进行沟通,同时也要从B的标准输出(stdout)读取信息。
- B则需要读取它的标准输入,并将内容打印出来。
这一切都要在不关闭流的情况下完成。
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 个回答
使用 communicate()
Python已经实现了communicate()
这个方法(它适用于A.py
,B.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
(通常是4090字节)。根据文档:
bufsize会作为相应的参数传递给io.open()函数,当创建stdin/stdout/stderr管道文件对象时: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()